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


169

想象一下这堂课

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发布代码,或者如果它是标准类型,则可以指定完全限定的类型名称?
TrueWill 2010年

您可以显示骨骼测试以显示您的想法吗?虽然我很高兴从您的角度出发,但从我们这方面来看,这似乎是一个没有花时间使问题可以回答而又无需长时间思考的人。
Ruben Bartelink 2010年

1
没有Foo或Bar()或类似的东西。只是一些演示代码来显示我所处的状态,而不会分散应用程序的细节。我只是得到了答案,我希望得到。
1

Answers:


283

您可以使用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);

如果您只想检查传入参数中的简单内容,也可以直接执行以下操作:

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

36
附带说明,如果您的函数有多个参数,则需要在通用Callback<>()Moq方法中指定所有类型。例如,如果您的方法具有定义Handler.AnsyncHandle(string, SomeResponse),则需要/* ... */.Callback<string, SomeResponse>(r => result = r);。我在很多地方都没有明确指出这一点,所以我想在这里添加它。
弗兰克·布莱斯

12
只是想更正@Frank,以防您未看到@JavaJudt答案。得到两个论点的正确方法是:/* ... */.Callback<string, SomeResponse>((s1, s2) => { str1 = s1; result = s2});
renatogbp

2
您也可以通过在lambda表达式中声明参数的类型来实现相同的目的,例如:.Callback((string s1, SomeResponse s2) => /* stuff */ )
jb637

1
您能否更新您的回复以包含对内置Capture.In帮助器的引用?

29

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

基本上,您只需要添加It.IsAny<>()具有适当类型的另一个,向该Callback方法添加另一个类型,并适当地更改lambda表达式即可。


22

回调方法当然可以工作,但是如果您在具有很多参数的方法上执行此操作,则可能会有些冗长。这是我用来删除某些样板的东西。

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

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

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


15
如果您只想检查是否使用已知参数调用了该方法,则该方法很好。在编写测试时尚未创建参数的情况下(例如,所讨论的单元在内部创建参数),使用Callback可以捕获和询问该参数,而您的方法则不能。
2014年

1
我需要存储传递的值,因为我需要验证传递的所有对象集。
MrFox

另外,有时参数是一个实体,并且您希望为该实体中的每个字段单独声明。最好的方法是捕获参数,而不是使用验证和匹配器。
Kevin Wong

12

另一种方法是使用的Capture.In功能moq。它是OOTB moq功能,可以在集合中捕获参数。

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

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

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

1
好答案!直到看到此答案,我才意识到Moq.Capture和Moq.CaptureMatch类。捕获是CallbackIMO 的更好替代方案。由于您直接在参数列表中使用Capture,因此在重构方法的参数列表时,它不太容易出现问题,从而使测试的脆性降低。使用Callback,您必须使安装程序中传递的参数与Callback所使用的类型参数保持同步,这肯定在过去引起了我的麻烦。
贾斯汀·霍尔泽

7

您可以使用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 )));

2

这也适用:

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

2

这里有很多好的答案!使用现成的Moq功能集,直到需要对传递给依赖项的几个类参数进行断言为止。如果最终遇到这种情况,它的Moq验证功能是匹配器不能很好地隔离测试失败,并且返回/回调方法捕获参数会在测试中添加不必要的代码行(并且对我来说,长时间测试是不行的。

这是要点: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提供的功能可以在声明时实现相同的功能,并提供故障隔离功能,那我会很感动。手指交叉!


1
我喜欢这个。Moq的验证与xUnit的Assert竞争执行断言的权限。在设置的最低订量部分上感觉不对。It.Is功能设置也应支持非表达式。
托马斯

“ Moq与xUnit的Assert竞争以执行断言的权限”-好说@Thomas。我还要补充一点,它将失去竞争。Moq可以很好地让您知道是否有匹配的呼叫,但是特定的断言可以为您提供更好的信息。我的方法的主要缺点是失去类型安全性和参数的顺序检查。一段时间以来,我一直在寻求对此的改进,希望那里有C#忍者可以一起破解示例!否则,如果我找到方法,将对此进行更新。
雅各布·麦凯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.