暫且不論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
準備後續使用期間VerifyAllExpectations
的expression
和times
。
在我展示Mock2
代碼並談論MockRepository
解決方案之前,先解釋一下關於單詞的解釋。這是很容易得到它的工作沒有返回值嘲笑方法的表達式都有一個類型,它是在嘲笑型通用。但對於具有返回值的方法,要調用的基礎驗證是Mock.Verify<TResult>(...)
。爲了能夠在VerifyAllExpectations
期間綁定到正確關閉的方法,我最終使用了反射。我確定修改Mock本身來放置這個功能將允許一個不太冒險的解決方案。
現在,回庫:我的想法是修改Mock2
所以它不是從Mock
繼承,它需要的Mock
實例作爲構造函數的參數,並使用該調用Setup
和Verify
。然後,你可以寫上MockRepository
(Create2
??)調用原來MockRepository.Create
並通過創建Mock
實例一Mock2
實例的構造函數,它然後返回一個新的擴展方法。
最後一種替代方法是在Mock
上添加SetupAndExpect
和VerifyAllExpectations
作爲擴展方法。但是,期望信息的存儲可能必須處於某種靜態狀態並面臨清理問題。
這裏的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>
。這些方法可能有一些更好的名稱和/或原因,首先這通常都是一個壞主意。
我希望這對你或某人有用!
公平的打Josh,謝謝。它在任何互聯網SCM上?如果是的話,我願意跟着它。乾杯。 – BanksySan
現在還沒有,但如果我這樣做,我會在這裏更新。 –