web-dev-qa-db-fra.com

Utilisation de Moq pour vérifier que les appels sont passés dans le bon ordre

J'ai besoin de tester la méthode suivante:

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

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

J'ai créé un Moq'd IWriter et je veux m'assurer que les méthodes Write() sont appelées dans le bon ordre.

J'ai le code de test suivant:

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

Cependant, le deuxième appel à Write() dans CreateOutput() (pour écrire la valeur id) renvoie un MockException avec le message "IWriter L'invocation de .Write () a échoué avec un comportement de simulation Strict. Toutes les invocations sur la simulation doivent avoir une configuration correspondante. ".

Je trouve également difficile de trouver une documentation/des exemples définitifs et à jour de séquences Moq.

Suis-je en train de faire quelque chose de mal ou ne puis-je pas configurer une séquence en utilisant la même méthode? Sinon, existe-t-il une alternative que je peux utiliser (de préférence en utilisant Moq/NUnit)?

53
g t

Il y a un bug quand en utilisant MockSequence sur le même mock . Il sera définitivement corrigé dans les versions ultérieures de la bibliothèque Moq (vous pouvez également le corriger manuellement en modifiant l'implémentation Moq.MethodCall.Matches).

Si vous souhaitez utiliser Moq uniquement, vous pouvez vérifier l'ordre des appels de méthode via des rappels:

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

J'ai réussi à obtenir le comportement que je veux, mais cela nécessite le téléchargement d'une bibliothèque tierce à partir de http://dpwhelan.com/blog/software-development/moq-sequences/

La séquence peut ensuite être testée à l'aide des éléments suivants:

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

J'ai ajouté cela comme réponse en partie pour aider à documenter cette solution, mais je suis toujours intéressé à savoir si quelque chose de similaire pourrait être réalisé en utilisant Moq 4.0 seul.

Je ne sais pas si Moq est toujours en développement, mais il serait bon de corriger le problème avec MockSequence, ou d'inclure l'extension moq-sequence dans Moq.

10
g t

J'ai écrit une méthode d'extension qui s'affirmera en fonction de l'ordre d'invocation.

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

Il fonctionne en tirant parti de la façon dont les fermetures fonctionnent en ce qui concerne les variables de portée. Puisqu'il n'y a qu'une seule déclaration pour sharedCallCount, toutes les fermetures auront une référence à la même variable. Avec attenduCallCount, une nouvelle instance est instanciée à chaque itération de la boucle (par opposition à simplement utiliser i dans la fermeture). De cette façon, chaque fermeture a une copie de i portée uniquement à elle-même pour la comparer avec le sharedCallCount lorsque les expressions sont appelées.

Voici un petit test unitaire pour l'extension. Notez que cette méthode est appelée dans votre section de configuration, pas dans votre section d'assertion.

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

Récemment, j'ai rassemblé deux fonctionnalités pour Moq: VerifyInSequence () et VerifyNotInSequence (). Ils fonctionnent même avec Loose Mocks. Cependant, ceux-ci ne sont disponibles que dans un fork du référentiel moq:

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

et attendez plus de commentaires et de tests avant de décider s'ils peuvent être inclus dans la version officielle de moq. Cependant, rien ne vous empêche de télécharger la source en tant que Zip, de la construire en DLL et de l'essayer. En utilisant ces fonctionnalités, la vérification de séquence dont vous avez besoin pourrait être écrite comme telle:

 var mockWriter = new Mock <IWriter> () {CallSequence = new LooseSequence ()}; 
 
 // effectuer les appels nécessaires 
 
 mockWriter.VerifyInSequence (x => x.Write (attenduType)); 
 mockWriter.VerifyInSequence (x => x.Write (attenduId)); 
 mockWriter.VerifyInSequence (x => x.Write (Expéditeur attendu)); 

(notez que vous pouvez utiliser deux autres séquences, selon vos besoins. Une séquence lâche permettra tous les appels entre ceux que vous voulez vérifier. StrictSequence ne le permettra pas et StrictAnytimeSequence est comme StrictSequence (aucun appel de méthode entre les appels vérifiés), mais permet la séquence devant être précédée d'un nombre quelconque d'appels arbitraires.

Si vous décidez d'essayer cette fonctionnalité expérimentale, veuillez commenter avec vos réflexions sur: https://github.com/Moq/moq4/issues/21

Merci!

4
Grzesiek Galezowski

La solution la plus simple serait d'utiliser un Queue :

var expectedParameters = new Queue<string>(new[]{expectedType,expectedId,expectedSender});
mockWriter.Setup(x => x.Write(expectedType))
          .Callback((string s) => Assert.AreEqual(expectedParameters.Dequeue(), s));
4

Je soupçonne qu'attendu, ce n'est pas ce que vous attendez.

Cependant, j'écrirais probablement ma propre implémentation d'IWriter pour vérifier dans ce cas ... probablement beaucoup plus facile (et plus facile à changer plus tard).

Désolé pour aucun conseil Moq directement. J'adore ça, mais je n'ai pas fait ça dedans.

avez-vous peut-être besoin d'ajouter .Verify () à la fin de chaque configuration? (C'est vraiment une supposition même si j'ai peur).

0
John Nicholas