期望多次调用方法

时间:2013-09-28 01:19:47

标签: moq moq-3

我如何告诉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);

基本上,我想在考试开始时设定我所有的期望,而不是让他们在不止一个地方。

1 个答案:

答案 0 :(得分: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的{​​{1}}的替代方法。 Times相当于VerifyAllExpectations。如果你愿意的话,你可以摆弄这些名字。

VerifyAll类存储Mock2expression传递给times,以备日后SetupAndExpect使用。

在我展示VerifyAllExpectations代码并谈论Mock2解决方案之前,请先解释一下关于wordiness的解释。让它适用于没有返回值的模拟方法是相当容易的,因为表达式都具有一个在模拟类型上是通用的类型。但是,对于具有返回值的方法,要调用的基础验证是MockRepository。为了能够在Mock.Verify<TResult>(...)期间绑定到正确关闭的方法,我最终使用了反射。我肯定会修改Mock本身,以便将这个功能放入其中,从而避免使用更少的hacky解决方案。

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

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

以下是VerifyAllExpectations代码:

Mock

最终警告:这只是经过轻微测试,未经同时测试。它没有等同于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。可能有一些更好的名称,因为这些方法和/或原因一开始通常都是一个坏主意。

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