如何使用 Moq 验证方法只调用了一次?

如何使用 Moq 验证方法只调用了一次?Verify()Verifable()之间的区别真的很令人困惑。

109672 次浏览

你可以使用 Times.Once()Times.Exactly(1):

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

下面是 时代杂志类的方法:

  • AtLeast-指定应以最小次数调用模拟方法。
  • AtLeastOnce-指定一个模拟方法应该被调用一次作为最小值。
  • AtMost-指定调用模拟方法的次数应为时间的最大值。
  • AtMostOnce-指定一个模拟方法应作为最大值调用一次。
  • Between-指定应在 from 和 to 时间之间调用模拟方法。
  • Exactly-指定调用模拟方法的次数正好是模拟方法的次数。
  • Never-指定不应调用模拟方法。
  • Once-指定只调用一次模拟方法。

只要记住它们是方法调用; 我总是被绊倒,认为它们是属性而忘记了括号。

测试控制器可以是:

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

当 DeleteCars 方法使用有效的 id 调用时,我们可以验证,这个测试只调用了一次 Service delete 方法:

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

假设我们正在构建一个计算器,其中使用一种方法来添加2个整数。让我们进一步假设需求是当 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。然而,作为一个好的实践,我总是设置它,因为我们可能需要强制方法的参数以满足某些期望,或者方法的返回值以满足某些期望,或者调用它的次数。