想象一下这门课

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?


你可以使用Mock。回调方法:

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

如果你只想检查传入的in参数中一些简单的东西,你也可以直接这样做:

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

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

验证功能非常强大,值得花时间去适应。


你可以使用它。is <TValue>()匹配器。

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

Gamlor的回答很适合我,但我认为我应该扩展John Carpenter的评论,因为我正在寻找一个涉及多个参数的解决方案。我想其他无意中看到这个页面的人可能也有类似的情况。我在Moq文档中找到了这个信息。

我将使用Gamlor的例子,但让我们假设AsyncHandle方法接受两个参数:一个字符串和一个somerresponse对象。

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表达式。


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

这也是可行的:

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

另一种方法是使用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特性集,直到需要对传递给依赖项的几个类参数进行断言。如果你最终在这种情况下,它的Moq验证功能。Is匹配器在隔离测试失败方面做得不好,捕获参数的Returns/Callback方式在测试中添加了不必要的代码行(长时间的测试对我来说是不可取的)。

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

        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提供了一个功能,既能完成同样的事情,又能提供声明性的故障隔离,我会很兴奋。祈祷!