Moq - 是否可以在设置中指定验证标准(例如,被称为时间)?

时间:2013-03-12 12:03:19

标签: testing mocking installation moq verify

如果您需要设置返回值,以及验证调用表达式的次数,您可以在一个语句中执行此操作吗?

从我可以收集的内容来看,Moq Setup(SomeExpression).Verifiable()Verify()一起调用,基本上是Verify(SomeExpression, Times.AtLeastOnce)?即它验证表达式只被调用。

这是一个更好地解释问题的例子。对于界面:

interface IFoo
{
    int ReturnSomething();
}

以下两个块是否等效(除了第一个将验证标记为可验证的所有设置)?

void Test()
{
    var mock = new Mock<IFoo>();
    mock.Setup((m) => m.ReturnSomething()).Returns(1).Verifiable();

    mock.Verify();
}

void Test()
{
    var mock = new Mock<IFoo>();
    mock.Setup((m) => m.ReturnSomething()).Returns(1);

    mock.Verify((m) => m.ReturnSomething(), Times.AtLeastOnce());
}

如果我想验证呼叫次数(比如说两次),这是唯一的方法,其中表达式会重复进行安装和验证吗?

void Test()
{
    var mock = new Mock<IFoo>();
    mock.Setup((m) => m.ReturnSomething()).Returns(1);

    mock.Verify((m) => m.ReturnSomething(), Times.Exactly(2));
}

我只是不喜欢打电话给安装和验证。好吧,因为这对AAA来说是一个好主意,换句话说,我不喜欢重复设置和验证的表达式。目前我将表达式存储在变量中并将其传递给每个方法,但感觉不那么干净。

PS - 上下文是用于检查何时更新缓存(expirations等)的测试。

4 个答案:

答案 0 :(得分:16)

要回答第一个问题,是的,这两个块是等价的。如果未调用模拟上的方法,则在调用.Verify时两者都将失败。

就我所知,您无法预先指定验证,如果您考虑一下,这是有道理的。

这是指定模拟的行为:

mock.Setup(m => m.ReturnSomething()).Returns(1);

这是验证来电者的行为:

mock.Verify(m => m.ReturnSomething(), Times.AtLeastOnce());

我个人更喜欢单独调用验证以确认调用者所需的行为,.Verifiable().Verify()是不太严格的快捷方式(他们只是检查方法被调用了一次或多次)但是如果您知道您的代码应该只调用一次方法,请将验证放在最后以进行确认。

我开始这样做之后,代码合并导致一个方法被调用两次,测试仍然通过,因为它被调用至少一次,但它也意味着其他事情多次发生,不应该有!

答案 1 :(得分:13)

我一直有这个问题。我使用严格的模拟,我想严格指定(即我使用It.Is<>()而不是It.IsAny())以及严格验证(即指定时间)。你遗憾地不能使用verifiable,因为Moq缺少Verifiable(Times)重载。

通话的完整表达(包括It.Is<>())通常很大。因此,为了避免重复,我通常采用以下方法:

Expression<Action<MockedType>> expression = mockedTypeInstance => mockedTypeInstance.MockedMethod(It.Is<TFirstArgument>(firstArgument => <some complex statement>)/*, ...*/);
_mock.Setup(expression);

/* run the test*/

_mock.Verify(expression, Times.Once);

不是非常易读,但我认为还没有其他方法可以使用严格的设置和严格的验证。

答案 2 :(得分:3)

在回答Evren Kuzucuoglu的回答时,我创建了以下扩展方法,使表达式更简单:

/// <summary>
/// Creates a method call expression that can be passed to both <see cref="Setup"/> and <see cref="Verify"/>.
/// </summary>
/// <typeparam name="T">Mocked object type.</typeparam>
/// <param name="mock">Mock of <see cref="T"/>.</param>
/// <param name="expression">Method call expression to record.</param>
/// <returns>Method call expression.</returns>
public static Expression<Action<T>> CallTo<T>(this Mock<T> mock, Expression<Action<T>> expression) where T : class
{
    return expression;
}

/// <summary>
/// Creates a method call expression that can be passed to both <see cref="Setup"/> and <see cref="Verify"/>.
/// </summary>
/// <typeparam name="T">Mocked object type.</typeparam>
/// <typeparam name="TResult">Method call return type.</typeparam>
/// <param name="mock">Mock of <see cref="T"/>.</param>
/// <param name="expression">Method call expression to record.</param>
/// <returns>Method call expression.</returns>
public static Expression<Func<T, TResult>> CallTo<T, TResult>(this Mock<T> mock, Expression<Func<T, TResult>> expression) where T : class
{
    return expression;
}

用法示例:

var createMapperCall = mockMappingFactory.CallTo(x => x.CreateMapper());
mockMappingFactory.Setup(createMapperCall).Returns(mockMapper.Object);

mockMappingFactory.Verify(createMapperCall, Times.Once());

答案 3 :(得分:2)

我创建了一个实用程序类来处理这个:

public class TestUtils
{
  private static List<Action> verifyActions = new List<Action>();

  public static void InitVerifyActions() => verifyActions = new List<Action>();

  public static void VerifyAllSetups()
  {
    foreach (var action in verifyActions)
    {
      action.Invoke();
    }
  }

  public static ISetup<T> SetupAndVerify<T>(Mock<T> mock, Expression<Action<T>> expression, Times times) where T : class
  {
    verifyActions.Add(() => mock.Verify(expression, times));
    return mock.Setup(expression);
  }

  public static ISetup<T, TResult> SetupAndVerify<T, TResult>(Mock<T> mock, Expression<Func<T, TResult>> expression, Times times) where T : class
  {
    verifyActions.Add(() => mock.Verify(expression, times));
    return mock.Setup(expression);
  }
}

然后在 TestInitialize() 中调用 TestUtils.InitVerifyActions(),然后在单元测试中:

TestUtils.SetupAndVerify(myMock, m => m.Foo("bar"), Times.Once()).Returns("baz");
TestUtils.SetupAndVerify(myOtherMock, m => m.Blah(), Times.Once());
...
TestUtils.VerifyAllSetups();