【问题标题】:Moq: How to get to a parameter passed to a method of a mocked serviceMoq:如何获取传递给模拟服务方法的参数
【发布时间】:2010-07-17 00:39:24
【问题描述】:

想象一下这个类

public class Foo {

    private Handler _h;

    public Foo(Handler h)
    {
        _h = h;
    }

    public void Bar(int i)
    {
        _h.AsyncHandle(CalcOn(i));
    }

    private SomeResponse CalcOn(int i)
    {
        ...;
    }
}

Mo(q)cking Handler 在 Foo 测试中,我如何能够检查 Bar() 传递给 _h.AsyncHandle 的内容?

【问题讨论】:

  • 您的意思是“AsyncHandle”(额外的“n”)吗?您能否发布 Handler 的代码,或者如果它是标准类型,请指定完全限定的类型名称?
  • 你能展示你的骨架测试来展示你的想法吗?虽然我很欣赏,从您的角度来看,这很明显,但从我们的角度来看,似乎有人没有花时间在没有做长长的推测性回答的情况下回答问题。
  • 既没有 Foo 也没有 Bar() ,也没有类似的东西。这只是一些演示代码,用于显示我所处的情况,而不会分散应用程序的细节。我得到了答案,我希望得到。

标签: c# moq


【解决方案1】:

您可以使用 Mock.Callback 方法:

var mock = new Mock<Handler>();
SomeResponse result = null;
mock.Setup(h => h.AsyncHandle(It.IsAny<SomeResponse>()))
    .Callback<SomeResponse>(r => result = r);

// do your test
new Foo(mock.Object).Bar(22);
Assert.NotNull(result);

如果你只想对传入的参数做一些简单的检查,你也可以直接做:

mock.Setup(h => h.AsyncHandle(It.Is<SomeResponse>(response => response != null)));

【讨论】:

  • 附注,如果您的函数有多个参数,则需要在通用 Callback&lt;&gt;() Moq 方法中指定所有类型。例如,如果您的方法具有定义Handler.AnsyncHandle(string, SomeResponse),则需要/* ... */.Callback&lt;string, SomeResponse&gt;(r =&gt; result = r);。我在很多地方都没有发现这一点,所以我想我会在这里添加它。
  • 只是想纠正@Frank,以防您没有看到@JavaJudt 的回答。获得两个参数的正确方法是:/* ... */.Callback&lt;string, SomeResponse&gt;((s1, s2) =&gt; { str1 = s1; result = s2});
  • 您也可以通过在 lambda 表达式中声明参数的类型来实现相同的目的,如下所示:.Callback((string s1, SomeResponse s2) =&gt; /* stuff */ )
  • 能否更新您的回复以包含对内置 Capture.In 助手的引用?
  • mockedObject.Invocations[0].Arguments[0] 从@Jeff Smith 访问参数的方式更方便。 @Ed Yablonsky 提到的 It.Is&lt;TValue&gt;() 匹配器也很有用。有了这些,您的断言可以是独立的,不需要外部变量。
【解决方案2】:

另一种方法是使用 Capture.In,这是 Moq 中的一个开箱即用功能,可让您将参数捕获到集合中:

//Arrange
var args = new List<SomeResponse>();
mock.Setup(h => h.AsyncHandle(Capture.In(args)));

//Act
new Foo(mock.Object).Bar(22);

//Assert
//... assert args.Single() or args.First()

【讨论】:

  • 很好的答案!在看到这个答案之前,我不知道 Moq.Capture 和 Moq.CaptureMatch 类。 Capture 是 CallbackIMO 的更好替代方案。由于您直接在参数列表中使用 Capture,因此在重构方法的参数列表时不太容易出现问题,因此使测试不那么脆弱。使用 Callback,您必须使 Setup 中传递的参数与用于 Callback 的类型参数保持同步,这在过去肯定给我带来了问题。
【解决方案3】:

Gamlor 的回答对我有用,但我想我会扩展 John Carpenter 的评论,因为我正在寻找一种涉及多个参数的解决方案。我认为偶然发现此页面的其他人可能处于类似情况。我在Moq documentation 找到了这个信息。

我将使用 Gamlor 的示例,但我们假设 AsyncHandle 方法接受两个参数:stringSomeResponse 对象。

var mock = new Mock<Handler>();
string stringResult = string.Empty;
SomeResponse someResponse = null;
mock.Setup(h => h.AsyncHandle(It.IsAny<string>(), It.IsAny<SomeResponse>()))
    .Callback<string, SomeResponse>((s, r) => 
    {
        stringResult = s;
        someResponse = r;
    });

// do your test
new Foo(mock.Object).Bar(22);
Assert.AreEqual("expected string", stringResult);
Assert.IsNotNull(someResponse);

基本上你只需要添加另一个具有适当类型的It.IsAny&lt;&gt;(),在Callback方法中添加另一个类型,并根据需要更改lambda表达式。

【讨论】:

    【解决方案4】:

    Callback 方法肯定会起作用,但是如果您在一个有很多参数的方法上执行此操作,它可能会有点冗长。这是我用来删除一些样板的东西。

    var mock = new Mock<Handler>();
    
    // do your test   
    new Foo(mock.Object).Bar(22);
    
    var arg = new ArgumentCaptor<SomeResponse>();
    mock.Verify(h => h.AsyncHandle(arg.Capture()));
    Assert.NotNull(arg.Value);
    

    这里是 ArgumentCaptor 的来源:

    public class ArgumentCaptor<T>
    {
        public T Capture()
        {
            return It.Is<T>(t => SaveValue(t));
        }
    
        private bool SaveValue(T t)
        {
            Value = t;
            return true;
        }
    
        public T Value { get; private set; }
    }
    

    【讨论】:

    • 如果有多个参数,如何从arg.Value中获取第二个变量?
    • 如果您有多个参数,您可以使用多个 ArgumentCaptor 实例。如果要测试来自多个调用的值,您可以使用 Capture.In 来自其他答案之一。
    【解决方案5】:

    Gamlor 的答案有效,但另一种方法(我认为在测试中更具表现力的方法)是...

    var mock = new Mock<Handler>();
    var desiredParam = 47; // this is what you want to be passed to AsyncHandle
    new Foo(mock.Object).Bar(22);
    mock.Verify(h => h.AsyncHandle(desiredParam), Times.Once());
    

    验证功能非常强大,值得花时间习惯。

    【讨论】:

    • 如果您只想检查是否使用已知参数调用了方法,则该方法很好。如果在编写测试时尚未创建参数(例如,相关单元在内部创建参数),则 Callback 使您能够捕获和询问它,而您的方法不会。
    • 我需要存储传递的值,因为我需要验证传递的所有对象集。
    • 另外,有时参数是一个实体,您希望为实体中的每个字段单独声明。最好的方法是捕获参数,而不是使用验证和匹配器。
    【解决方案6】:

    你可以使用It.Is&lt;TValue&gt;()匹配器。

    var mock = new Mock<Handler>();
    new Foo(mock.Object).Bar(22);
    mock.Verify(h => h.AsyncHandle(It.Is<SomeResponse>(r => r != null )));
    

    【讨论】:

    • 有一个类似但不同的场景,这适用于。试图测试一个方法是否作为字符串传递了一个(生成的)Guid。能够做到Guid tempGuid; mock.Verify(m =&gt; m.SomeMethod(It.Is&lt;string&gt;(s =&gt; Guid.TryParse(s, out tempGuid)));完美。
    【解决方案7】:

    这也有效:

    Mock<InterfaceThing> mockedObject = new Mock<InterfaceThing>();
    var objectParameter = mockedObject.Invocations[1].Arguments[0] as ObjectParameter;
    

    【讨论】:

    • 这是一种比官方接受的答案更优雅、更方便的访问模拟方法参数的方法,尤其是当您有多个参数时。 @Ed Yablonsky 提到的 It.Is&lt;TValue&gt;() 匹配器也非常有用
    【解决方案8】:

    这里有很多很好的答案!使用开箱即用的 Moq 功能集,直到您需要对传递给依赖项的几个类参数进行断言。但是,如果您最终遇到这种情况,带有 It.Is 匹配器的 Moq 验证功能并不能很好地隔离测试失败,并且捕获参数的 Returns/Callback 方式会在您的测试中添加不必要的代码行(以及长时间的测试对我来说是不行的)。

    这里有一个要点:https://gist.github.com/Jacob-McKay/8b8d41ebb9565f5fca23654fd944ac6b 我写了一个 Moq (4.12) 扩展,它提供了一种更具声明性的方式来对传递给模拟的参数进行断言,而没有上述缺点。以下是验证部分现在的样子:

            mockDependency
                .CheckMethodWasCalledOnce(nameof(IExampleDependency.PersistThings))
                .WithArg<InThing2>(inThing2 =>
                {
                    Assert.Equal("Input Data with Important additional data", inThing2.Prop1);
                    Assert.Equal("I need a trim", inThing2.Prop2);
                })
                .AndArg<InThing3>(inThing3 =>
                {
                    Assert.Equal("Important Default Value", inThing3.Prop1);
                    Assert.Equal("I NEED TO BE UPPER CASED", inThing3.Prop2);
                });
    

    如果 Moq 提供了一个功能来完成同样的事情,同时又是声明性的并提供了这样的故障隔离,我会很兴奋。手指交叉!

    【讨论】:

    • 我喜欢这个。 Moq 的 Verify 与 xUnit 的 Assert 竞争执行断言的权限。这在设置的最小起订量部分感觉不对。 It.Is 功能设置也应该支持非表达式。
    • “Moq 与 xUnit 的 Assert 竞争执行断言的权限”——@Thomas 说得好。我要补充一点,它会输掉比赛。 Moq 擅长让您知道是否有匹配的呼叫,但特定的断言可以为您提供更好的信息。我的方法的主要缺点是失去了类型安全和参数的顺序检查。我一直在寻找对此的改进,希望有一个 C# 忍者可以一起破解一个示例!否则,如果我找到一种方法,我会更新它。
    • @JacobMcKay 我自己试过了,现在您可以指定要与 lamda 表达式一起使用的重载并检查具有相同类型的多个参数。使用检查具有相同类型的参数的顺序。在这里你可以找到我的修改:link.
    • @Alex 太酷了!传递一个表达式更像是 Moq-ish,谢谢你的叉子!我仍在寻找对匹配断言与参数的松散性的改进。我想要一个回调方法,它通过与表达式匹配的类型安全的参数传递,不知道 C# 是否可能
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-24
    • 2019-06-08
    • 1970-01-01
    • 2015-05-24
    • 2012-12-12
    • 1970-01-01
    相关资源
    最近更新 更多