Jak sprawdzić, czy metoda została wywołana dokładnie raz za pomocą Moq?

112

Jak sprawdzić, czy metoda została wywołana dokładnie raz za pomocą Moq? Sprawa Verify()vs. Verifable()jest naprawdę zagmatwana.

Josh Kodroff
źródło

Odpowiedzi:

165

Możesz użyć Times.Once()lub Times.Exactly(1):

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

Oto metody w klasie Times :

  • AtLeast - Określa, że ​​symulowana metoda powinna być wywoływana razy minimum.
  • AtLeastOnce - określa, że ​​symulowana metoda powinna być wywoływana co najmniej jeden raz.
  • AtMost - Określa, że ​​symulowana metoda powinna być wywoływana w maksymalnych odstępach czasu.
  • AtMostOnce - określa, że ​​symulowana metoda powinna być wywoływana maksymalnie jeden raz.
  • Between - określa, że ​​mockowana metoda powinna być wywoływana między czasami.
  • Exactly - określa, że ​​symulowana metoda powinna być wywoływana dokładnie razy.
  • Never - określa, że ​​nie należy wywoływać fałszywej metody.
  • Once - określa, że ​​symulowana metoda powinna być wywoływana dokładnie jeden raz.

Pamiętaj tylko, że są to wywołania metod; Ciągle się potykałem, myśląc, że to właściwości i zapominając o nawiasach.

Jeff Ogata
źródło
2
więc jak uzyskać / skonfigurować mockContext?
Choco
2
@Choco Zakładam, że to tylko jego Mock. Więc to było coś w rodzaju var mockContext = new Mock<IContext>()ustawienia tego.
Zack Huber
Zastanawiam się tylko, jak AtLeast, AtMost, Between, lub Exactlymogą być postrzegane jako własność. Chodzi mi o to, że potrzebują parametru, aby coś zrobić.
Danylo Yelizarov
8

Wyobraź sobie, że tworzymy kalkulator z jedną metodą dodawania 2 liczb całkowitych. Wyobraźmy sobie dalej, że wymaganie polega na tym, że wywołanie metody add powoduje jednokrotne wywołanie metody print. Oto jak byśmy to przetestowali:

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

A oto rzeczywisty test z komentarzami w kodzie w celu dalszego wyjaśnienia:

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

Uwaga : Domyślnie Moq usunie wszystkie właściwości i metody, gdy tylko utworzysz obiekt Mock. Więc nawet bez wywoływania Setup, Moq już zdeponował metody, IPrinterwięc możesz po prostu wywołać Verify. Jednak jako dobrą praktykę zawsze ją konfiguruję, ponieważ może być konieczne wymuszenie parametrów metody, aby spełnić określone oczekiwania, lub wartość zwracaną z metody w celu spełnienia określonych oczekiwań lub liczbę jej wywołań.

KodowanieYoshi
źródło
Dzwoniłem Verify, Times.Oncenigdy nie dzwoniłem Setup. Z pewnością spodziewałbym Verifysię wybuchu w takim przypadku, ale tak się nie stało.
dudeNumber4
@ dudeNumber4 Nie, to nie wybuchnie, ponieważ domyślnie Moq zgubi wszystkie właściwości i metody, gdy tylko utworzysz Mockobiekt. Więc nawet bez wywoływania Setup, Moq już zdeponował metody, IPrinterwięc możesz po prostu wywołać Verify. Jednak w ramach dobrej praktyki zawsze ją konfiguruję, ponieważ może być konieczne wymuszenie parametrów metody lub wartości zwracanej z metody.
CodingYoshi
Przepraszam, to było straszne wyjaśnienie. Zadzwoniłem Times.Exactly(1)i nie zawiodło się, gdy metoda została faktycznie wywołana dwukrotnie. Dopiero po dodaniu Setupdla danej metody nie udało się to poprawnie.
dudeNumber4
2

Kontrolerem testu może być:

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

A gdy metoda DeleteCars zostanie wywołana z poprawnym identyfikatorem, możemy to sprawdzić, metoda Service remove została wywołana dokładnie raz przez ten test:

 [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));
    }
sanjeev bhusal
źródło