2013-09-28 68 views
1

我怎麼能告訴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

暫且不論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替換允許Times傳遞。該VerifyAllExpectations是一個相當於VerifyAll。如果你願意,你可以用這些名字來擺弄。

Mock2類存儲傳遞到SetupAndExpect準備後續使用期間VerifyAllExpectationsexpressiontimes

在我展示Mock2代碼並談論MockRepository解決方案之前,先解釋一下關於單詞的解釋。這是很容易得到它的工作沒有返回值嘲笑方法的表達式都有一個類型,它是在嘲笑型通用。但對於具有返回值的方法,要調用的基礎驗證是Mock.Verify<TResult>(...)。爲了能夠在VerifyAllExpectations期間綁定到正確關閉的方法,我最終使用了反射。我確定修改Mock本身來放置這個功能將允許一個不太冒險的解決方案。

現在,回庫:我的想法是修改Mock2所以它不是從Mock繼承,它需要的Mock實例作爲構造函數的參數,並使用該調用SetupVerify。然後,你可以寫上MockRepositoryCreate2 ??)調用原來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<Times>。這些方法可能有一些更好的名稱和/或原因,首先這通常都是一個壞主意。

我希望這對你或某人有用!

+0

公平的打Josh,謝謝。它在任何互聯網SCM上?如果是的話,我願意跟着它。乾杯。 – BanksySan

+0

現在還沒有,但如果我這樣做,我會在這裏更新。 –