C# 在测试异步方法时如何使用序列?
我正在使用并且在测试异步方法时遇到问题。 当我这样做时:C# 在测试异步方法时如何使用序列?,c#,unit-testing,asynchronous,async-await,moq,C#,Unit Testing,Asynchronous,Async Await,Moq,我正在使用并且在测试异步方法时遇到问题。 当我这样做时: [Test] public async Task Demo() { using (Sequence.Create()) { _fooMock.Setup(f => f.Fooxiate()).InSequence(); _barMock.Setup(b => b.Baronize()).InSequence(); var result = aw
[Test]
public async Task Demo()
{
using (Sequence.Create())
{
_fooMock.Setup(f => f.Fooxiate()).InSequence();
_barMock.Setup(b => b.Baronize()).InSequence();
var result = await _cut.DoMyStuffAsync();
Assert.AreEqual("someString", result);
}
}
当调用_foo.Fooxiate()
说:
Moq.Sequences.SequenceUsageException:“只能使用使用MockSequence.Create()创建的活动MockSequence调用模拟调用”
我是做错了什么,还是不支持在异步方法中测试调用序列?
以下是完整的演示代码,包括上述生产代码:
using System.Threading.Tasks;
using Moq;
using Moq.Sequences;
using NUnit.Framework;
namespace TestingAsync.Tests
{
[TestFixture]
public class SomeClassTests
{
private SomeClass _cut;
private Mock<IFoo> _fooMock;
private Mock<IBar> _barMock;
[SetUp]
public void Setup()
{
_fooMock = new Mock<IFoo>();
_barMock = new Mock<IBar>();
_cut = new SomeClass(_fooMock.Object, _barMock.Object);
}
[Test]
public async Task Demo()
{
using (Sequence.Create())
{
_fooMock.Setup(f => f.Fooxiate()).InSequence();
_barMock.Setup(b => b.Baronize()).InSequence();
var result = await _cut.DoMyStuffAsync();
Assert.AreEqual("someString", result);
}
}
}
public class SomeClass
{
private readonly IFoo _foo;
private readonly IBar _bar;
public SomeClass(IFoo foo, IBar bar)
{
_bar = bar;
_foo = foo;
}
public async Task<string> DoMyStuffAsync()
{
return await Task.Run(() => DoMyStuff());
}
private string DoMyStuff()
{
_foo.Fooxiate();
_bar.Baronize();
return "someString";
}
}
public interface IBar
{
void Baronize();
}
public interface IFoo
{
void Fooxiate();
}
}
使用System.Threading.Tasks;
使用最小起订量;
使用Moq.序列;
使用NUnit.Framework;
命名空间TestingAsync.Tests
{
[测试夹具]
公共类测试
{
二等兵;;
私人模拟(fooMock),;
私人模拟(barMock),;
[设置]
公共作废设置()
{
_fooMock=新Mock();
_barMock=新Mock();
_cut=新的SomeClass(\u fooMock.Object,\u barMock.Object);
}
[测试]
公共异步任务演示()
{
使用(Sequence.Create())
{
_fooMock.Setup(f=>f.Fooxiate()).InSequence();
_Setup(b=>b.Baronize()).InSequence();
var result=await_cut.domysuffasync();
AreEqual(“someString”,result);
}
}
}
公共类
{
私有只读iFooFoo;
专用只读IBar\u条;
公共SomeClass(IFoo-foo、IBar)
{
_巴=巴;
_foo=foo;
}
公共异步任务domysuffasync()
{
返回等待任务。运行(()=>domysuff());
}
私有字符串domysuff()
{
_foo.Fooxiate();
_巴罗尼兹();
返回“someString”;
}
}
公共接口IBar
{
void Baronize();
}
公共接口IFoo
{
void Fooxiate();
}
}
Moq.Sequences不被写入多线程,因为它使用[ThreadStatic]
属性来跟踪环境序列
[ThreadStatic]
private static Sequence instance;
结果是仅为当前线程存储环境序列。然后调用Task.Run
,生成一个后台线程来执行工作。这导致抛出异常,因为该线程的实例为null
if (Instance == null)
throw new SequenceUsageException(context + " can only be called with an active MockSequence created with MockSequence.Create()");
Moq.Sequences无法保证异步代码中调用的顺序,因为:
[ThreadStatic]
而无法正确支持异步
/等待
根据OP的请求,我更新了该库,以更好地支持现代并发编程模式。(希望现在人们使用任务
s编程,而不是线程
s。)
从2.1.0版开始,您可以使用AsyncLocal
而不是[ThreadStatic]
变量使Moq.Sequences跟踪环境序列。这意味着环境序列可以跨异步边界“流动”,例如等待
,并且在延续中仍然可见(可能在不同的线程上运行)
出于向后兼容性的原因,您当前需要在运行任何测试之前通过执行以下操作来选择新行为:
Sequence.ContextMode = SequenceContextMode.Async;
在撰写本文时,新的行为还没有经过广泛的测试。感谢您添加此stakx!令人印象深刻:)我以前不知道AsyncLocal,谢谢你教我@约翰宾德尔:这是一个相当新的添加到.NET(4.6)中,我只是在不久前从令人敬畏的Castle DynamicProxy团队那里了解到的。在.NET4.6之前,还有一种更为神秘的东西:
CallContext
。玩得开心。:)再次感谢斯塔克斯@我把这个答案标为(现在)正确答案。