如何验证Moq一次调用过一个方法?


Answers:


165

您可以使用Times.Once(),或Times.Exactly(1)

mockContext.Verify(x => x.SaveChanges(), Times.Once());
mockContext.Verify(x => x.SaveChanges(), Times.Exactly(1));

以下是Times类的方法:

  • AtLeast -指定模拟方法的调用次数应为最小次数。
  • AtLeastOnce -指定模拟方法至少应调用一次。
  • AtMost -指定应以最大时间调用模拟方法。
  • AtMostOnce -指定模拟方法最多应调用一次。
  • Between -指定应在时间之间和时间之间调用模拟方法。
  • Exactly -指定模拟方法应精确地调用次。
  • Never -指定不应调用模拟方法。
  • Once -指定模拟方法应只被调用一次。

只要记住它们是方法调用即可;我一直绊倒,以为它们是属性,却忘记了括号。


2
那么如何获取/设置模拟上下文呢?
Choco

2
@Choco我认为那只是他的Mock实例。因此,这有点像var mockContext = new Mock<IContext>()设置。
扎克·胡伯

我只是想知道AtLeastAtMostBetween,或Exactly可作为物业查看。我的意思是,他们obv需要一个参数来做某事。
Danylo Yelizarov

8

想象一下,我们正在用一种方法将两个整数相加来构建一个计算器。让我们进一步想象一下,要求是当调用add方法时,它将调用一次print方法。这是我们将如何测试的方法:

public interface IPrinter
{
    void Print(int answer);
}

public class ConsolePrinter : IPrinter
{
    public void Print(int answer)
    {
        Console.WriteLine("The answer is {0}.", answer);
    }
}

public class Calculator
{
    private IPrinter printer;
    public Calculator(IPrinter printer)
    {
        this.printer = printer;
    }

    public void Add(int num1, int num2)
    {
        printer.Print(num1 + num2);
    }
}

这是实际测试,其中包含代码中的注释,以进一步阐明:

[TestClass]
public class CalculatorTests
{
    [TestMethod]
    public void WhenAddIsCalled__ItShouldCallPrint()
    {
        /* Arrange */
        var iPrinterMock = new Mock<IPrinter>();

        // Let's mock the method so when it is called, we handle it
        iPrinterMock.Setup(x => x.Print(It.IsAny<int>()));

        // Create the calculator and pass the mocked printer to it
        var calculator = new Calculator(iPrinterMock.Object);

        /* Act */
        calculator.Add(1, 1);

        /* Assert */
        // Let's make sure that the calculator's Add method called printer.Print. Here we are making sure it is called once but this is optional
        iPrinterMock.Verify(x => x.Print(It.IsAny<int>()), Times.Once);

        // Or we can be more specific and ensure that Print was called with the correct parameter.
        iPrinterMock.Verify(x => x.Print(3), Times.Once);
    }
}

注意:默认情况下,一旦创建Mock对象,Moq就会对所有属性和方法进行存根。因此,即使没有调用Setup,Moq也已将方法存入其中,IPrinter以便您可以调用Verify。但是,作为一个好的实践,我总是进行设置,因为我们可能需要对方法强制使用参数以满足特定的期望,或者对方法的返回值进行满足特定的期望或调用它的次数。


我在打电话VerifyTimes.Once从未打电话Setup。我当然希望Verify在那种情况下会爆炸,但事实并非如此。
dudeNumber4

@ dudeNumber4不,它不会爆炸,因为默认情况下,Moq会在创建Mock对象后立即对所有属性和方法进行存根。因此,即使没有调用Setup,Moq也已将方法存入其中,IPrinter以便您可以调用Verify。但是,作为一种好的做法,我总是对其进行设置,因为我们可能需要对方法强制使用参数或对方法返回值。
CodingYoshi19年

抱歉,这是一个糟糕的解释。我打电话Times.Exactly(1),并没有当方法实际上调用了两次失败。只有在添加Setup了所讨论的方法后,它才能正确失败。
dudeNumber4

2

测试控制器可能是:

  public HttpResponseMessage DeleteCars(HttpRequestMessage request, int id)
    {
        Car item = _service.Get(id);
        if (item == null)
        {
            return request.CreateResponse(HttpStatusCode.NotFound);
        }

        _service.Remove(id);
        return request.CreateResponse(HttpStatusCode.OK);
    }

然后,当使用有效ID调用DeleteCars方法时,我们可以验证一下,此测试恰好一次调用了Service remove方法:

 [TestMethod]
    public void Delete_WhenInvokedWithValidId_ShouldBeCalledRevomeOnce()
    {
        //arange
        const int carid = 10;
        var car = new Car() { Id = carid, Year = 2001, Model = "TTT", Make = "CAR 1", Price=2000 };
        mockCarService.Setup(x => x.Get(It.IsAny<int>())).Returns(car);

        var httpRequestMessage = new HttpRequestMessage();
        httpRequestMessage.Properties[HttpPropertyKeys.HttpConfigurationKey] = new HttpConfiguration();

        //act
        var result = carController.DeleteCar(httpRequestMessage, vechileId);

        //assert
        mockCarService.Verify(x => x.Remove(carid), Times.Exactly(1));
    }
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.