在大量调用之后,Moq很难验证依赖性

时间:2013-05-28 16:13:11

标签: c# unit-testing moq

当使用Moq模拟一个被称为很多次的依赖时,我遇到了麻烦。当我拨打Verify时,Moq需要很长时间(几分钟)来回复,有时会遇到NullReferenceException(我想这是可以理解的,考虑到Moq会有多少数据必须积累来从“冷启动”中做Verify。)

所以我的问题是,是否有另一种策略我可以使用Moq来做这件事,或者我应该为这个相当不寻常的案例恢复手工制作的存根。具体来说,有没有办法告诉Moq我只对验证参数的特定过滤器以及忽略所有其他值感兴趣?

以下两种方法都不令人满意。

给定CUT和Dep

public interface ISomeInterface
{
    void SomeMethod(int someValue);
}

public class ClassUnderTest
{
    private readonly ISomeInterface _dep;
    public ClassUnderTest(ISomeInterface dep)
    {
        _dep = dep;
    }

    public void DoWork()
    {
        for (var i = 0; i < 1000000; i++) // Large number of calls to dep
        {
            _dep.SomeMethod(i);
        }
    }
}

Moq策略1 - 验证

        var mockSF = new Mock<ISomeInterface>();
        var cut = new ClassUnderTest(mockSF.Object);
        cut.DoWork();
        mockSF.Verify(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == 12345)),
                      Times.Once());
        mockSF.Verify(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == -1)),
                      Times.Never());

Moq策略2 - 回拨

        var mockSF = new Mock<ISomeInterface>();
        var cut = new ClassUnderTest(mockSF.Object);
        bool isGoodValueAlreadyUsed = false;
        mockSF.Setup(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == 12345)))
              .Callback(() =>
            {
                if (isGoodValueAlreadyUsed)
                {
                    throw new InvalidOperationException();
                }
                isGoodValueAlreadyUsed = true;
            });
        mockSF.Setup(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == -1)))
              .Callback(() =>
                { throw new InvalidOperationException(); });

        cut.DoWork();
        Assert.IsTrue(isGoodValueAlreadyUsed);

1 个答案:

答案 0 :(得分:3)

通常当达到这样的限制时,我会重新考虑我的设计(没有冒犯,我看到你的代表)。看起来被测试的方法做了太多工作,这违反了单一责任原则。它首先生成一个大的项目列表,然后验证为每个项目调用一个worker,同时还验证该序列包含正确的元素。

我将功能拆分为序列生成器,并验证序列是否具有正确的元素,以及另一个作用于序列的方法,并验证它是否为每个元素执行了worker:

namespace StackOverflowExample.Moq
{
    public interface ISequenceGenerator
    {
        IEnumerable<int> GetSequence();
    }

    public class SequenceGenrator : ISequenceGenerator
    {
        public IEnumerable<int> GetSequence()
        {
            var list = new List<int>();
            for (var i = 0; i < 1000000; i++) // Large number of calls to dep
            {
                list.Add(i);
            }
            return list;
        }
    }

    public interface ISomeInterface
    {
        void SomeMethod(int someValue);
    }

    public class ClassUnderTest
    {
        private readonly ISequenceGenerator _generator;
        private readonly ISomeInterface _dep;

        public ClassUnderTest(ISomeInterface dep, ISequenceGenerator generator)
        {
            _dep = dep;
            _generator = generator;
        }

        public void DoWork()
        {
            foreach (var i in _generator.GetSequence())
            {
                _dep.SomeMethod(i);
            }
        }
    }

    [TestFixture]
    public class LargeSequence
    {
        [Test]
        public void SequenceGenerator_should_()
        {
            //arrange
            var generator = new SequenceGenrator();

            //act
            var list = generator.GetSequence();

            //assert
            list.Should().Not.Contain(-1);
            Executing.This(() => list.Single(i => i == 12345)).Should().NotThrow();
            //any other assertions
        }

        [Test]
        public void DoWork_should_perform_action_on_each_element_from_generator()
        {
            //arrange
            var items = new List<int> {1, 2, 3}; //can use autofixture to generate random lists
            var generator = Mock.Of<ISequenceGenerator>(g => g.GetSequence() == items);
            var mockSF = new Mock<ISomeInterface>();

            var classUnderTest = new ClassUnderTest(mockSF.Object, generator);

            //act
            classUnderTest.DoWork();

            //assert
            foreach (var item in items)
            {
                mockSF.Verify(c=>c.SomeMethod(item), Times.Once());
            }
        }
    }
}

修改 可以混合不同的方法来定义特定的期望,包括。 When(),已过时的AtMost()MockBehavior.StrictCallback等。

同样,Moq不适用于大型集合,因此存在性能损失。您还可以使用其他方法来验证将哪些数据传递给模拟器。

对于OP中的示例,这是一个简化的设置:

var mockSF = new Mock<ISomeInterface>(MockBehavior.Strict);
var cnt = 0;

mockSF.Setup(m => m.SomeMethod(It.Is<int>(i => i != -1)));
mockSF.Setup(m => m.SomeMethod(It.Is<int>(i => i == 12345))).Callback(() =>cnt++).AtMostOnce();

对于多于一次的12次调用,这将抛出-1,并且可以在cnt != 0上进行断言。