¿Pueden ayudarme a comprender la devolución de llamada de Moq?

95

Usando Moq y miré Callbackpero no he podido encontrar un ejemplo simple para entender cómo usarlo.

¿Tiene un pequeño fragmento de trabajo que explique claramente cómo y cuándo usarlo?

user9969
fuente

Respuestas:

82

Difícil de superar https://github.com/Moq/moq4/wiki/Quickstart

Si eso no es lo suficientemente claro, lo llamaría un error de documentación ...

EDITAR: En respuesta a su aclaración ...

Por cada método simulado Setupque realice, podrá indicar cosas como:

  • restricciones en los insumos
  • el valor de / forma en que se deriva el valor de retorno (si lo hay)

El .Callbackmecanismo dice "No puedo describirlo en este momento, pero cuando suceda una llamada con esta forma, llámame y haré lo que sea necesario". Como parte de la misma cadena de llamadas fluida, puedes controlar el resultado que se devolverá (si lo hay) a través de .Returns". En los ejemplos de QS, un ejemplo es que hacen que el valor devuelto aumente cada vez.

En general, no necesitará un mecanismo como este con mucha frecuencia (los patrones de prueba de xUnit tienen términos para antipatrones de la clase Conditional Logic In Tests), y si hay alguna forma más simple o incorporada de establecer lo que necesita, debería ser utilizado con preferencia.

La parte 3 de 4 en la serie Moq de Justin Etheredge lo cubre, y hay otro ejemplo de devoluciones de llamada aquí.

Puede encontrar un ejemplo simple de devolución de llamada en Uso de devoluciones de llamada con publicación de Moq .

Ruben Bartelink
fuente
3
Hola Ruben, estoy aprendiendo Moq y, si quieres, estoy construyendo muchos ejemplos para entender cómo hacer las cosas usándolo. Mi problema es que no entiendo cuándo usarlo. Una vez que entienda que el problema está resuelto, escribiré mi propio código. Si lo explicaras con tus propias palabras, ¿cuándo usarías la devolución de llamada? gracias aprecio su tiempo
user9969
15
¿Difícil de superar [enlace]? De ningún modo. Ese enlace le muestra cómo hacer docenas de cosas diferentes, pero no le dice por qué necesita hacer alguna de ellas. Lo cual es un problema común en la documentación burlona, ​​he descubierto. Puedo contar con cero dedos la cantidad de explicaciones claras y buenas de TDD + burlas que he encontrado. La mayoría asume un nivel de conocimiento que, si lo tuviera, no necesitaría leer el artículo.
Ryan Lundy
@Kyralessa: Acepto tu punto. Personalmente, tenía bastante conocimiento de libros, así que encontré las cosas de inicio rápido absolutamente perfectas. Desafortunadamente, no conozco un ejemplo mejor que los que vinculé al final de la publicación. Si encuentra uno, publíquelo aquí y estaré encantado de editarlo en (o siéntase libre de hacerlo usted mismo)
Ruben Bartelink
"Haré lo que sea necesario y te diré el resultado a devolver (si lo hay)" Creo que esto es engañoso, AFAIU Callbackno tiene nada que ver con el valor de retorno (a menos que lo vincules a través del código). Básicamente, solo se asegura de que se llame a la devolución de llamada antes o después de cada invocación (dependiendo de si la encadenó antes o después Returnsrespectivamente), simple y llanamente.
Ohad Schneider
1
@OhadSchneider Siguiendo mi enlace ... ¡tienes razón! Me pregunto (pero no estoy lo suficientemente interesado, ya que no he usado Moq durante mucho tiempo) si la interfaz de Fluent ha cambiado (no parece probable, es decir, hice una suposición equivocada y no leí lo que vinculé como normalmente lo resolvería de autocompletar yo mismo). Espero que la solución aborde su punto, avíseme si no es así
Ruben Bartelink
59

A continuación, se muestra un ejemplo del uso de una devolución de llamada para probar una entidad enviada a un servicio de datos que maneja una inserción.

var mock = new Mock<IDataService>();
DataEntity insertedEntity = null;

mock.Setup(x => x.Insert(It.IsAny<DataEntity>())).Returns(1) 
           .Callback((DataEntity de) => insertedEntity = de);

Sintaxis alternativa del método genérico:

mock.Setup(x => x.Insert(It.IsAny<DataEntity>())).Returns(1) 
           .Callback<DataEntity>(de => insertedEntity = de);

Entonces puedes probar algo como

Assert.AreEqual("test", insertedEntity.Description, "Wrong Description");
Jeff Hall
fuente
4
Podría decirse que para ese caso particular (dependiendo de si está tratando de expresar pruebas contra el estado o el comportamiento), en algunos casos puede ser más limpio usar un It.Is<T>in a en Mock.Verifylugar de ensuciar la prueba con temps. Pero +1 porque apuesto a que hay mucha gente que trabajará mejor con un ejemplo.
Ruben Bartelink
10

Hay dos tipos de Callbacken Moq. Uno ocurre antes de que vuelva la llamada; el otro ocurre después de que regresa la llamada.

var message = "";
mock.Setup(foo => foo.Execute(arg1: "ping", arg2: "pong"))
    .Callback((x, y) =>
    {
        message = "Rally on!";
        Console.WriteLine($"args before returns {x} {y}");
    })
    .Returns(message) // Rally on!
    .Callback((x, y) =>
    {
        message = "Rally over!";
        Console.WriteLine("arg after returns {x} {y}");
    });

En ambas devoluciones de llamada, podemos:

  1. inspeccionar los argumentos del método
  2. capturar argumentos del método
  3. cambiar el estado contextual
Shaun Luttin
fuente
2
En realidad, ambos ocurren antes de que regrese la llamada (en lo que respecta a la persona que llama). Consulte stackoverflow.com/a/28727099/67824 .
Ohad Schneider
5

Callbackes simplemente un medio para ejecutar cualquier código personalizado que desee cuando se realiza una llamada a uno de los métodos de la simulación. He aquí un ejemplo sencillo:

public interface IFoo
{
    int Bar(bool b);
}

var mock = new Mock<IFoo>();

mock.Setup(mc => mc.Bar(It.IsAny<bool>()))
    .Callback<bool>(b => Console.WriteLine("Bar called with: " + b))
    .Returns(42);

var ret = mock.Object.Bar(true);
Console.WriteLine("Result: " + ret);

// output:
// Bar called with: True
// Result: 42

Recientemente me encontré con un caso de uso interesante para él. Suponga que espera algunas llamadas a su simulacro, pero ocurren al mismo tiempo. Por lo tanto, no tiene forma de saber el orden en el que se llamarían, pero desea saber las llamadas que esperaba que se realizaran (independientemente del orden). Puedes hacer algo como esto:

var cq = new ConcurrentQueue<bool>();
mock.Setup(f => f.Bar(It.IsAny<bool>())).Callback<bool>(cq.Enqueue);
Parallel.Invoke(() => mock.Object.Bar(true), () => mock.Object.Bar(false));
Console.WriteLine("Invocations: " + String.Join(", ", cq));

// output:
// Invocations: True, False

Por cierto, no se confunda con la distinción engañosa "antes Returns" y "después Returns". Es simplemente una distinción técnica de si su código personalizado se ejecutará después de Returnshaber sido evaluado o antes. A los ojos de la persona que llama, ambos se ejecutarán antes de que se devuelva el valor. De hecho, si el método es void-returning, ni siquiera puede llamar Returnsy, sin embargo, funciona igual. Para obtener más información, consulte https://stackoverflow.com/a/28727099/67824 .

Ohad Schneider
fuente
1

Además de las otras buenas respuestas aquí, lo he usado para realizar lógica antes de lanzar una excepción. Por ejemplo, necesitaba almacenar todos los objetos que se pasaron a un método para su posterior verificación, y ese método (en algunos casos de prueba) necesitaba lanzar una excepción. Llamando .Throws(...)en Mock.Setup(...)anula la Callback()acción y nunca se lo llama. Sin embargo, al lanzar una excepción dentro de la devolución de llamada, aún puede hacer todas las cosas buenas que una devolución de llamada tiene para ofrecer y aún así lanzar una excepción.

Frank Bryce
fuente