45

If you need to Setup a return value, as well as Verify how many times the expression was called, can you do this in one statement?

From what I can gather, Moq's Setup(SomeExpression).Verifiable() called along with Verify(), basically does a Verify(SomeExpression, Times.AtLeastOnce)? i.e. it verifys the expression was called only.

Here's an example to explain the question better. For an interface:

interface IFoo
{
    int ReturnSomething();
}

Are the following two blocks equivalent (other than the first will Verify all setups marked as verifiable)?

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

    mock.Verify();
}

and

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

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

If I wanted to verify the number of calls (say twice), is this the only way, where the expression is repeated for the Setup and Verify?

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

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

I just don't like having to call Setup and Verify. Well, since this is a good idea for AAA, to rephrase, I don't like having to repeat the expression for the Setup and Verify. At the moment I store the expression in a variable and pass it to each method, but doesn't feel so clean.

PS - The context for this is for a test checking when a cache is updated or not (expirations etc.)

4

4 回答 4

24

我一直有这个问题。我使用严格的模拟,我想严格指定(即我使用It.Is<>()而不是It.IsAny())以及严格验证(即指定时间)。可悲的是,您不能为此使用可验证的,因为最小起订量缺少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);

不是非常可读,但我认为没有另一种方法可以同时使用严格设置和严格验证。

于 2016-04-20T13:28:24.860 回答
16

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

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

这是指定模拟的行为:

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

这是验证调用者的行为:

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

就我个人而言,我更喜欢单独调用 verify 来确认调用者所需的行为,.Verifiable()and.Verify()是不太严格的快捷方式(他们只是检查方法被调用一次或多次)但是如果你知道你的代码应该只调用一次方法,把最后验证以确认它。

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

于 2013-03-12T12:28:17.863 回答
7

根据 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());
于 2018-05-22T20:40:49.660 回答
3

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

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();
于 2021-04-20T20:09:58.657 回答