¿Cómo verifico que se haya llamado a un método exactamente una vez con Moq?

112

¿Cómo verifico que se haya llamado a un método exactamente una vez con Moq? La cosa Verify()vs. Verifable()es realmente confusa.

Josh Kodroff
fuente

Respuestas:

165

Puede usar Times.Once(), o Times.Exactly(1):

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

Estos son los métodos de la clase Times :

  • AtLeast - Especifica que un método simulado debe invocarse veces como mínimo.
  • AtLeastOnce - Especifica que un método simulado debe invocarse una vez como mínimo.
  • AtMost - Especifica que un método simulado debe invocarse por tiempo como máximo.
  • AtMostOnce - Especifica que un método simulado debe invocarse una vez como máximo.
  • Between - Especifica que se debe invocar un método simulado entre las horas desde y hasta.
  • Exactly - Especifica que un método simulado debe invocarse exactamente las veces.
  • Never - Especifica que no se debe invocar un método simulado.
  • Once - Especifica que un método simulado debe invocarse exactamente una vez.

Recuerde que son llamadas a métodos; Seguí tropezando, pensando que eran propiedades y olvidándome del paréntesis.

Jeff Ogata
fuente
2
entonces, ¿cómo se obtiene / configura el mockContext?
Chocó
2
@Choco Supongo que es solo su instancia de Mock. Así que fue algo así como var mockContext = new Mock<IContext>()configurar eso.
Zack Huber
Me pregunto cómo AtLeast, AtMost, Between, o Exactlypodría ser visto como una propiedad. Quiero decir, obviamente necesitan un parámetro para hacer algo.
Danylo Yelizarov
8

Imagina que estamos construyendo una calculadora con un método para sumar 2 enteros. Imaginemos además que el requisito es que cuando se llama al método add, llama al método print una vez. Así es como probaríamos esto:

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

Y aquí está la prueba real con comentarios dentro del código para mayor aclaración:

[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 : De forma predeterminada, Moq eliminará todas las propiedades y métodos tan pronto como cree un objeto Mock. Entonces, incluso sin llamar Setup, Moq ya ha eliminado los métodos para IPrinterque pueda llamar Verify. Sin embargo, como buena práctica, siempre lo configuro porque es posible que necesitemos hacer cumplir los parámetros del método para cumplir con ciertas expectativas, o el valor de retorno del método para cumplir con ciertas expectativas o la cantidad de veces que se ha llamado.

CodificaciónYoshi
fuente
Estaba llamando Verify, Times.Oncesin llamar nunca Setup. Ciertamente esperaría Verifyexplotar en ese caso, pero no fue así.
dudeNumber4
@ dudeNumber4 No, no explotará porque, de forma predeterminada, Moq eliminará todas las propiedades y métodos tan pronto como cree un Mockobjeto. Entonces, incluso sin llamar Setup, Moq ya ha eliminado los métodos para IPrinterque pueda llamar Verify. Sin embargo, como buena práctica, siempre lo configuro porque es posible que necesitemos aplicar los parámetros al método o el valor de retorno del método.
CodingYoshi
Lo siento, esa fue una explicación terrible. Llamé Times.Exactly(1)y no falló cuando el método se llamó dos veces. Solo después de agregar Setupel método en cuestión, falló correctamente.
dudeNumber4
2

El controlador de prueba puede 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);
    }

Y cuando se llama al método DeleteCars con una identificación válida, entonces podemos verificar que, el método de eliminación del servicio se llama exactamente una vez por esta prueba:

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