Moq:如何获取传递给模拟服务方法的参数

Jan*_*Jan 149 c# moq

想象一下这堂课

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)
    {
        ...;
    }
}
Run Code Online (Sandbox Code Playgroud)

Mo(q)cking Handler在Foo的测试中,我怎样才能检查Bar()传递给了_h.AsyncHandle什么?

Gam*_*lor 251

您可以使用Mock.Callback方法:

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

// do your test
new Foo(mock.Object).Bar(22);
Assert.NotNull(result);
Run Code Online (Sandbox Code Playgroud)

如果您只想在传入的参数中检查一些简单的东西,您也可以直接执行:

mock.Setup(h => h.AnsyncHandle(It.Is<SomeResponse>(response => response != null)));
Run Code Online (Sandbox Code Playgroud)

  • 另请注意,如果函数有多个参数,则需要在泛型`Callback <>()`Moq方法中指定所有类型.例如,如果你的方法有定义`Handler.AnsyncHandle(string,SomeResponse)`,你需要`/*...*/.Callback <string,SomeResponse>(r => result = r);`.我没有在很多地方明确说明这一点,所以我想我会在这里添加它. (33认同)
  • 只是想在没有看到@JavaJudt答案的情况下纠正@Frank.得到两个参数的正确方法是:`/*...*/.回调<string,SomeResponse>((s1,s2)=> {str1 = s1; result = s2});` (9认同)
  • 您也可以通过在 lambda 表达式中声明参数的类型来实现相同的效果,如下所示:`.Callback((string s1, SomeResponse s2) =&gt; /* stuff */ )` (8认同)
  • 您能否更新您的回复以包含对内置 `Capture.In` 帮助程序的引用? (2认同)

Joh*_*nny 48

另一种方法是使用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()
Run Code Online (Sandbox Code Playgroud)

  • 很好的答案!在看到这个答案之前,我不知道 Moq.Capture 和 Moq.CaptureMatch 类。在我看来,捕获是“回调”的更好替代方案。由于直接在参数列表中使用 Capture,因此在重构方法的参数列表时不太容易出现问题,从而使测试变得不那么脆弱。使用回调,您必须保持在设置中传递的参数与用于回调的类型参数同步,这在过去肯定给我带来了问题。 (2认同)

Pet*_*tin 21

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());
Run Code Online (Sandbox Code Playgroud)

验证非常强大,值得花时间习惯.

  • 如果您只想检查是否使用已知参数调用方法,那么这种方法很好.如果在编写测试时尚未创建参数(例如,有问题的单元在内部创建参数),则Callback使您能够捕获并查询此参数,而您的方法则不会. (13认同)

Jav*_*udt 18

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

我将使用Gamlor的示例,但让我们假装AsyncHandle方法有两个参数:a string和一个SomeResponse对象.

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);
Run Code Online (Sandbox Code Playgroud)

基本上,您只需要It.IsAny<>()使用适当的类型添加另一个类型,向该Callback方法添加另一个类型,并根据需要更改lambda表达式.


And*_*ord 18

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);
Run Code Online (Sandbox Code Playgroud)

以下是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; }
}
Run Code Online (Sandbox Code Playgroud)


小智 7

这也有效:

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

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

Edw*_*sky 5

您可以使用It.Is<TValue>()匹配器。

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