我如何告诉Moq期望多次通话,以便我仍然可以使用MockRepository
到VerifyAll
,如下所示?
[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);
基本上,我想在考试开始时设定我所有的期望,而不是让他们在不止一个地方。
答案 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
类存储Mock2
和expression
传递给times
,以备日后SetupAndExpect
使用。
在我展示VerifyAllExpectations
代码并谈论Mock2
解决方案之前,请先解释一下关于wordiness的解释。让它适用于没有返回值的模拟方法是相当容易的,因为表达式都具有一个在模拟类型上是通用的类型。但是,对于具有返回值的方法,要调用的基础验证是MockRepository
。为了能够在Mock.Verify<TResult>(...)
期间绑定到正确关闭的方法,我最终使用了反射。我肯定会修改Mock本身,以便将这个功能放入其中,从而避免使用更少的hacky解决方案。
现在,回到存储库:我的想法是修改VerifyAllExpectations
,以便不是从Mock2
继承,而是将Mock
的实例作为构造函数参数并使用它来致电Mock
和Setup
。然后你可以在Verify
(MockRepository
??)上编写一个新的扩展方法,调用原始的Create2
并将创建的MockRepository.Create
实例传递给{{1}的构造函数然后返回的实例。
最后一种方法是在Mock
上添加Mock2
和SetupAndExpect
作为扩展方法。但是,期望信息的存储可能必须处于某种静态状态并面临清理问题。
以下是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
。可能有一些更好的名称,因为这些方法和/或原因一开始通常都是一个坏主意。
我希望它对你或某人有用!