Como posso verificar se um método foi chamado exatamente uma vez com o Moq?

112

Como posso verificar se um método foi chamado exatamente uma vez com o Moq? A coisa Verify()vs. Verifable()é realmente confusa.

Josh Kodroff
fonte

Respostas:

165

Você pode usar Times.Once(), ou Times.Exactly(1):

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

Aqui estão os métodos da classe Times :

  • AtLeast - Especifica que um método simulado deve ser invocado vezes, no mínimo.
  • AtLeastOnce - Especifica que um método simulado deve ser chamado pelo menos uma vez.
  • AtMost - Especifica que um método simulado deve ser invocado vezes no máximo.
  • AtMostOnce - Especifica que um método simulado deve ser chamado uma vez, no máximo.
  • Between - Especifica que um método simulado deve ser chamado entre os horários de e para.
  • Exactly - Especifica que um método simulado deve ser chamado exatamente vezes.
  • Never - Especifica que um método simulado não deve ser chamado.
  • Once - Especifica que um método simulado deve ser chamado exatamente uma vez.

Lembre-se de que são chamadas de método; Eu ficava tropeçando, pensando que eram propriedades e esquecendo os parênteses.

Jeff Ogata
fonte
2
então, como você obtém / configura o mockContext?
Choco
2
@Choco Presumo que seja apenas sua instância Mock. Então foi algo parecido var mockContext = new Mock<IContext>()com isso.
Zack Huber
Eu só quero saber como AtLeast, AtMost, Between, ou Exactlypoderia ser visto como propriedade. Quer dizer, eles obviamente precisam de um parâmetro para fazer algo.
Danylo Yelizarov
8

Imagine que estamos construindo uma calculadora com um método para adicionar 2 inteiros. Vamos imaginar ainda que o requisito é que, quando o método add é chamado, ele chama o método print uma vez. Aqui está como testaríamos isso:

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

E aqui está o teste real com comentários dentro do código para maiores esclarecimentos:

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

Nota : Por padrão, o Moq fará o stub de todas as propriedades e métodos assim que você criar um objeto Mock. Portanto, mesmo sem chamar Setup, o Moq já criou os métodos de para IPrinterque você possa apenas chamar Verify. No entanto, como uma boa prática, sempre o configuro porque podemos precisar impor os parâmetros do método para atender a certas expectativas, ou o valor de retorno do método para atender a certas expectativas ou o número de vezes que ele foi chamado.

CodingYoshi
fonte
Eu estava ligando Verify, Times.Oncesem nunca ligar Setup. Eu certamente esperaria Verifyexplodir nesse caso, mas isso não aconteceu.
dudeNumber4
@ dudeNumber4 Não, não explodirá porque, por padrão, o Moq fará o stub de todas as propriedades e métodos assim que você criar um Mockobjeto. Portanto, mesmo sem chamar Setup, o Moq já criou os métodos de para IPrinterque você possa apenas chamar Verify. No entanto, como uma boa prática, eu sempre o configuro porque pode ser necessário impor os parâmetros ao método ou o valor de retorno do método.
CodingYoshi
Desculpe, essa foi uma explicação terrível. Liguei Times.Exactly(1)e não falhou quando o método foi de fato chamado duas vezes. Somente após adicionar Setuppara o método em questão, ele falhou corretamente.
dudeNumber4
2

O controlador de teste pode ser:

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

E quando o método DeleteCars é chamado com um id válido, podemos verificar se o método Service remove chamado exatamente uma vez por este teste:

 [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
fonte