【问题标题】:Expect multiple calls to method期望多次调用方法
【发布时间】:2013-09-28 01:19:46
【问题描述】:

我如何告诉 Moq 期待多个电话,以便我仍然可以使用 MockRepositoryVerifyAll,如下所示?

[TestFixture]
public class TestClass
{
    [SetUp]
    public void SetUp()
    {
        _mockRepository = new MockRepository(MockBehavior.Strict);
        _mockThing = _mockRepository.Create<IThing>();

        _sut = new Sut(_mockThing.Object);
    }

    [TearDown]
    public void TearDown()
    {
        _mockRepository.VerifyAll();
    }

    private Mock<IThing> _mockThing;

    private MockRepository _mockRepository;

    [Test]
    public void TestManyCalls(Cell cell)
    {
       _mockThing.SetUp(x => x.DoSomething()).Return(new DoneStatus());
    }
}

我知道您可以在验证时执行此操作,但我必须独立验证所有内容。有没有办法告诉它会发生什么,而不是在事件发生后验证它?

类似于:

_mockThing.SetUp(x => x.DoSomething()).Return(new DoneStatus()).Times(20);

基本上,我想在测试开始时设定我所有的期望,而不是将它们放在多个地方。

【问题讨论】:

    标签: moq moq-3


    【解决方案1】:

    暂时将 MockRepository 放在一边,我创建了一个继承自 Mock 的类,以提供您所追求的功能。一、用法(XUnit语法):

        [Fact]
        public void SomeTest()
        {
            var mock = new Mock2<IDependency>();
            var sut = new Sut(mock.Object);
            mock.SetupAndExpect(d => d.DoSomething(It.IsAny<string>(), It.IsAny<long>()),Times.Once).Returns(3);
            mock.SetupAndExpect(d => d.DoSomethingNoReturn(It.IsAny<string>()), Times.Once);
            mock.SetupAndExpect(d => d.DoSomethingNoReturn(It.IsAny<string>()), Times.Once);
            sut.CallDoSomething();
            mock.VerifyAllExpectations();
        }
    

    SetupAndExpect 方法是 Setup 的替代品,它允许传递 TimesVerifyAllExpectations 等效于 VerifyAll。如果你愿意,你可以摆弄这些名字。

    Mock2 类存储传递给SetupAndExpectexpressiontimes,以供以后在VerifyAllExpectations 期间使用。

    在我展示Mock2 代码并讨论MockRepository 解决方案之前,先解释一下冗长的内容。让它适用于没有返回值的模拟方法是相当容易的,因为表达式都有一个比模拟类型更通用的类型。但是,对于具有返回值的方法,要调用的基础验证是 Mock.Verify&lt;TResult&gt;(...)。为了能够在VerifyAllExpectations 期间绑定到正确关闭的方法,我最终使用了反射。我确信修改 Mock 本身以添加此功能将允许一个不那么 hacky 的解决方案。

    现在,回到存储库:我的想法是修改Mock2,以便它不是从Mock 继承,而是将Mock 的实例作为构造函数参数并使用它来调用Setup 和@ 987654341@。然后你可以在MockRepository (Create2??) 上编写一个新的扩展方法,它调用原始的MockRepository.Create 并将创建的Mock 实例传递给Mock2 实例的构造函数,然后它返回。

    对此的最后一种替代方法是在Mock 上添加SetupAndExpectVerifyAllExpectations 作为扩展方法。但是,期望信息的存储可能必须处于某种静态状态并面临清理问题。

    这是Mock2 代码:

    public class Mock2<T> : Mock<T> where T:class
    {
        private readonly Dictionary<Type, List<Tuple<Expression,Func<Times>>>> _resultTypeKeyedVerifications =
            new Dictionary<Type, List<Tuple<Expression, Func<Times>>>>();
    
        private readonly List<Tuple<Expression<Action<T>>, Func<Times>>> _noReturnTypeVerifications = 
            new List<Tuple<Expression<Action<T>>, Func<Times>>>();
    
        public ISetup<T, TResult> SetupAndExpect<TResult>(Expression<Func<T, TResult>> expression, Func<Times> times)
        {
            // Store the expression for verifying in VerifyAllExpectations
            var verificationsForType = GetVerificationsForType(typeof(TResult));
            verificationsForType.Add(new Tuple<Expression, Func<Times>>(expression, times));
    
            // Continue with normal setup
            return Setup(expression);
        }
    
        public ISetup<T> SetupAndExpect(Expression<Action<T>> expression, Func<Times> times)
        {
            _noReturnTypeVerifications.Add(new Tuple<Expression<Action<T>>, Func<Times>>(expression, times));
            return Setup(expression);
        }
    
        private List<Tuple<Expression, Func<Times>>> GetVerificationsForType(Type type)
        {
            // Simply gets a list of verification info for a particular return type,
            // creating it and putting it in the dictionary if it doesn't exist.
            if (!_resultTypeKeyedVerifications.ContainsKey(type))
            {
                var verificationsForType = new List<Tuple<Expression, Func<Times>>>();
                _resultTypeKeyedVerifications.Add(type, verificationsForType);
            }
            return _resultTypeKeyedVerifications[type];
        }
    
        /// <summary>
        /// Use this instead of VerifyAll for setups perfomed using SetupAndRespect
        /// </summary>
        public void VerifyAllExpectations()
        {
            VerifyAllWithoutReturnType();
            VerifyAllWithReturnType();
        }
    
        private void VerifyAllWithoutReturnType()
        {
            foreach (var noReturnTypeVerification in _noReturnTypeVerifications)
            {
                var expression = noReturnTypeVerification.Item1;
                var times = noReturnTypeVerification.Item2;
                Verify(expression, times);
            }
        }
    
        private void VerifyAllWithReturnType()
        {
            foreach (var typeAndVerifications in _resultTypeKeyedVerifications)
            {
                var returnType = typeAndVerifications.Key;
                var verifications = typeAndVerifications.Value;
    
                foreach (var verification in verifications)
                {
                    var expression = verification.Item1;
                    var times = verification.Item2;
    
                    // Use reflection to find the Verify method that takes an Expression of Func of T, TResult
                    var verifyFuncMethod = GetType()
                        .GetMethods(BindingFlags.Instance | BindingFlags.Public)
                        .Single(IsVerifyMethodForReturnTypeAndFuncOfTimes)
                        .MakeGenericMethod(returnType);
    
                    // Equivalent to Verify(expression, times)
                    verifyFuncMethod.Invoke(this, new object[] {expression, times});
                }
            }
        }
    
        private static bool IsVerifyMethodForReturnTypeAndFuncOfTimes(MethodInfo m)
        {
            if (m.Name != "Verify") return false;
            // Look for the single overload with two funcs, which is the one we want
            // as we're looking at verifications for functions, not actions, and the
            // overload we're looking for takes a Func<Times> as the second parameter
            var parameters = m.GetParameters();
            return parameters.Length == 2
                   && parameters[0] // expression
                       .ParameterType // Expression
                       .GenericTypeArguments[0] // Func
                       .Name == "Func`2"
                   && parameters[1] // times
                       .ParameterType // Func
                       .Name == "Func`1";
        }
    }
    

    最后警告:这只是轻微测试,不会同时测试。它没有等效于Verify 的重载,它采用Times 而不是Func&lt;Times&gt;。可能有一些更好的方法名称和/或为什么这通常是一个坏主意的原因。

    我希望它对你或某人有用!

    【讨论】:

    • 公平竞争乔希,谢谢。它在任何互联网单片机上吗?如果是这样,我会撒谎跟随它。干杯。
    猜你喜欢
    • 1970-01-01
    • 2020-03-18
    • 1970-01-01
    • 1970-01-01
    • 2014-11-21
    • 1970-01-01
    • 2016-10-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多