【问题标题】:Moq - Is it possible to specify in a Setup the Verify criteria (e.g. Times called)?起订量 - 是否可以在设置中指定验证标准(例如调用次数)?
【发布时间】:2013-03-12 12:03:19
【问题描述】:

如果您需要设置返回值,以及验证表达式被调用了多少次,您可以在一条语句中完成吗?

据我所知,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 - 上下文用于测试检查缓存何时更新(过期等)

【问题讨论】:

  • 这是使用最小起订量 4(我看过旧代码 sn-ps,其中有 ISetup 上的方法,例如 AtMostOnce)

标签: testing mocking installation moq verify


【解决方案1】:

我一直有这个问题。我使用严格的模拟,我想严格指定(即我使用It.Is&lt;&gt;() 而不是It.IsAny())以及严格验证(即指定时间)。遗憾的是,您不能为此使用可验证的,因为 Moq 缺少 Verifiable(Times) 过载。

调用的完整表达式,包括It.Is&lt;&gt;(),一般都很大。因此,为了避免重复,我通常会采取以下措施:

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);

可读性不强,但我认为没有其他方法可以同时使用严格设置和严格验证。

【讨论】:

  • 使用单独的表达式来重复使用非常适合我的需要。不错的解决方案!
  • 好主意,但是如果我希望设置返回一个值,如何使用它?
  • @ishayle 像往常一样做,使用流畅的最小起订量语法,例如_mock.Setup(表达式).Returns(...)
  • 我们是否还需要在较新版本的 moq 中指定设置和验证(用于断言 Times)?或任何改变或不同的方式?
【解决方案2】:

要回答第一个问题,是的,这两个块是等价的。如果没有调用 mock 上的方法,则调用 .Verify 时两者都会失败。

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

这是指定模拟的行为:

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

这是在验证调用者的行为:

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

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

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

【讨论】:

  • 很遗憾,考虑到 Rhino Mocks 支持在单个语句中进行设置和验证。我认为这将是最常见的用例之一(如果不是 the 最常见的),所以我很困惑,即使是现在,三年多以来它还没有包含在框架中之后。我想我会改用 Rhino Mocks...
【解决方案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());

【讨论】:

    【解决方案4】:

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

    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();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-19
      • 1970-01-01
      • 2015-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多