Usar Moq para verificar que las llamadas se realicen en el orden correcto


Necesito probar el siguiente método:

CreateOutput(IWriter writer)
{
    writer.Write(type);
    writer.Write(id);
    writer.Write(sender);

    // many more Write()s...
}

He creado un Moq'd IWriter y quiero asegurarme de que los métodos Write() se llaman en el orden correcto.

Tengo el siguiente código de prueba:

var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
var sequence = new MockSequence();
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedType));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedId));
mockWriter.InSequence(sequence).Setup(x => x.Write(expectedSender));

Sin embargo, la segunda llamada a Write() en CreateOutput() (para escribir el valor id) lanza un MockException con el mensaje "iWriter.La invocación Write () falló con un comportamiento falso Estricto. Todas las invocaciones en el mock deben tener una configuración correspondiente.".

También lo estoy encontrando es difícil encontrar documentación/ejemplos definitivos y actualizados de secuencias de Moq.

¿Estoy haciendo algo mal, o no puedo configurar una secuencia usando el mismo método? Si no, ¿ hay alguna alternativa que pueda usar (preferiblemente usando Moq / NUnit)?

Author: g t, 2012-05-15

6 answers

Hay un error cuando usa MockSequence en el mismo mock. Definitivamente se arreglará en versiones posteriores de la biblioteca Moq (también puede arreglarlo manualmente cambiando Moq.MethodCall.Coincide con la implementación).

Si solo desea usar Moq, puede verificar el orden de llamada del método a través de devoluciones de llamada:

int callOrder = 0;
writerMock.Setup(x => x.Write(expectedType)).Callback(() => Assert.That(callOrder++, Is.EqualTo(0)));
writerMock.Setup(x => x.Write(expectedId)).Callback(() => Assert.That(callOrder++, Is.EqualTo(1)));
writerMock.Setup(x => x.Write(expectedSender)).Callback(() => Assert.That(callOrder++, Is.EqualTo(2)));
 45
Author: Sergey Berezovskiy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-15 22:08:46

He logrado obtener el comportamiento que quiero, pero requiere descargar una biblioteca de terceros desde http://dpwhelan.com/blog/software-development/moq-sequences /

La secuencia se puede probar usando lo siguiente:

var mockWriter = new Mock<IWriter>(MockBehavior.Strict);
using (Sequence.Create())
{
    mockWriter.Setup(x => x.Write(expectedType)).InSequence();
    mockWriter.Setup(x => x.Write(expectedId)).InSequence();
    mockWriter.Setup(x => x.Write(expectedSender)).InSequence();
}

He añadido esto como una respuesta en parte para ayudar a documentar esta solución, pero todavía estoy interesado en si algo similar se podría lograr usando Moq 4.0 solo.

No estoy seguro de si Moq todavía está en desarrollo, pero solucionando el problema con el MockSequence, o incluyendo la extensión de moq-secuencias en Moq sería bueno ver.

 9
Author: g t,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-15 14:33:15

Escribí un método de extensión que afirmará basado en el orden de invocación.

public static class MockExtensions
{
  public static void ExpectsInOrder<T>(this Mock<T> mock, params Expression<Action<T>>[] expressions) where T : class
  {
    // All closures have the same instance of sharedCallCount
    var sharedCallCount = 0;
    for (var i = 0; i < expressions.Length; i++)
    {
      // Each closure has it's own instance of expectedCallCount
      var expectedCallCount = i;
      mock.Setup(expressions[i]).Callback(
        () =>
          {
            Assert.AreEqual(expectedCallCount, sharedCallCount);
            sharedCallCount++;
          });
    }
  }
}

Funciona aprovechando la forma en que los cierres funcionan con respecto a las variables de ámbito. Dado que solo hay una declaración para sharedCallCount, todos los cierres tendrán una referencia a la misma variable. Con expectedCallCount, se crea una instancia nueva cada iteración del bucle (en lugar de simplemente usar i en el cierre). De esta manera, cada cierre tiene una copia de i scoped solo para se compara con el sharedCallCount cuando se invocan las expresiones.

Aquí hay una pequeña prueba unitaria para la extensión. Tenga en cuenta que este método se llama en la sección de configuración, no en la sección de aserción.

[TestFixture]
public class MockExtensionsTest
{
  [TestCase]
  {
    // Setup
    var mock = new Mock<IAmAnInterface>();
    mock.ExpectsInOrder(
      x => x.MyMethod("1"),
      x => x.MyMethod("2"));

    // Fake the object being called in order
    mock.Object.MyMethod("1");
    mock.Object.MyMethod("2");
  }

  [TestCase]
  {
    // Setup
    var mock = new Mock<IAmAnInterface>();
    mock.ExpectsInOrder(
      x => x.MyMethod("1"),
      x => x.MyMethod("2"));

    // Fake the object being called out of order
    Assert.Throws<AssertionException>(() => mock.Object.MyMethod("2"));
  }
}

public interface IAmAnInterface
{
  void MyMethod(string param);
}
 7
Author: Justin Ryder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-31 16:20:46

Recientemente, he reunido dos características para Moq: VerifyInSequence() y VerifyNotInSequence(). Trabajan incluso con Burlas Sueltas. Sin embargo, estos solo están disponibles en una bifurcación de repositorio moq:

Https://github.com/grzesiek-galezowski/moq4

Y esperar más comentarios y pruebas antes de decidir si se pueden incluir en oficial moq releaase. Sin embargo, nada le impide descargar la fuente como ZIP, construirla en una dll y darle una oportunidad. Utilizar estas características, la verificación de secuencia que necesita podría escribirse como tal:

var mockWriter = new Mock<IWriter>() { CallSequence = new LooseSequence() };

//perform the necessary calls

mockWriter.VerifyInSequence(x => x.Write(expectedType));
mockWriter.VerifyInSequence(x => x.Write(expectedId));
mockWriter.VerifyInSequence(x => x.Write(expectedSender));

(tenga en cuenta que puede usar otras dos secuencias, dependiendo de sus necesidades. Secuencia suelta permitirá cualquier llamada entre los que desea verificar. StrictSequence no permitirá esto y StrictAnytimeSequence es como StrictSequence (no hay llamadas a métodos entre llamadas verificadas), pero permite que la secuencia sea precedida por cualquier número de llamadas arbitrarias.

Si decide dar a esta característica experimental un pruebe, por favor comente con sus pensamientos sobre: https://github.com/Moq/moq4/issues/21

Gracias!

 3
Author: Grzesiek Galezowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-29 17:07:18

La solución más simple sería usar una cola :

var expectedParameters = new Queue<string>(new[]{expectedType,expectedId,expectedSender});
mockWriter.Setup(x => x.Write(expectedType))
          .Callback((string s) => Assert.AreEqual(expectedParameters.Dequeue(), s));
 2
Author: Ufuk Hacıoğulları,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-25 23:25:41

Sospecho que expectedId no es lo que esperas.

Sin embargo, probablemente solo escribiría mi propia implementación de iWriter para verificar en este caso ... probablemente mucho más fácil (y más fácil de cambiar más adelante).

Lo siento por ningún consejo Moq directamente. Me encanta, pero no he hecho esto en él.

Tal vez necesite agregar .Verify() al final de cada configuración? (Eso realmente es una suposición, aunque me temo).

 0
Author: John Nicholas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-15 14:03:55