测试异步方法时如何使用序列?

时间:2017-07-07 10:35:39

标签: c# unit-testing asynchronous async-await moq

我正在使用Moq.Sequences而我在测试异步方法时遇到问题。 当我这样做时:

[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);
    }
}

calling _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();
    }
}

2 个答案:

答案 0 :(得分:4)

This other answer正确解释了由于使用了{async / await,Moq.Sequences 没有没有正确支持[ThreadStatic] / Task {1}}。

根据OP的要求,我更新了该库,以便为现代并发编程模式提供更好的支持。 (希望人们最近使用Thread进行编程,而不是AsyncLocal<Sequence> s。)

从版本2.1.0开始,您可以使用[ThreadStatic]而不是await变量使Moq.Sequences跟踪环境序列。这意味着环境序列可以&#34;流动&#34;跨越异步边界,例如Sequence.ContextMode = SequenceContextMode.Async; ,并且在延续中仍然可见(可能在不同的线程上运行)。

出于向后兼容性的原因,您目前需要在运行任何测试之前执行以下操作来选择新行为:

{{1}}

在撰写本文时,新行为尚未经过广泛测试so issue and bug reports are welcome

答案 1 :(得分:2)

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()");

https://github.com/dwhelan/Moq-Sequences → src/Moq.Sequences/Sequence.cs

Moq.Sequences没有一种方法可以保证异步代码中的调用顺序,因为:

  1. 并发代码通常没有确定性的执行顺序。
  2. Async是对线程的抽象,因为它比线程更难以预测。有许多技术导致非确定性调用序列,例如使用Task.Run在后台线程中工作,使用Parallel.For / ForEach,使用TPL数据流,使用Task.WhenAll等。