200 votes

Vérification d'un paramètre spécifique avec Moq

public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
{
    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    var queueableMessage = CreateSingleQueueableMessage();
    var message = queueableMessage[0];
    var xml = QueueableMessageAsXml(queueableMessage);
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(xml)).Verifiable();
    //messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();

    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(essageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});

    //messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(xml), Times.Once());
    messageServiceClientMock.Verify();
}

Je commence à utiliser Moq et j'ai un peu de mal. J'essaie de vérifier que messageServiceClient reçoit le bon paramètre, qui est un XmlElement, mais je ne trouve aucun moyen de le faire fonctionner. Cela fonctionne uniquement lorsque je ne vérifie pas une valeur particulière.

Des idées ?

Réponse partielle : J'ai trouvé un moyen de tester que le xml envoyé au proxy est correct, mais je ne pense toujours pas que ce soit la bonne façon de procéder.

public void SubmitMessagesToQueue_OneMessage_SubmitSuccessfully()
{
    var messageServiceClientMock = new Mock<IMessageServiceClient>();
    messageServiceClientMock.Setup(proxy => proxy.SubmitMessage(It.IsAny<XmlElement>())).Verifiable();
    var serviceProxyFactoryStub = new Mock<IMessageServiceClientFactory>();
    serviceProxyFactoryStub.Setup(proxyFactory => proxyFactory.CreateProxy()).Returns(messageServiceClientMock.Object);
    var loggerStub = new Mock<ILogger>();

    var client = new MessageClient(serviceProxyFactoryStub.Object, loggerStub.Object);
    var message = CreateMessage();
    client.SubmitMessagesToQueue(new List<IMessageRequestDTO> {message});

    messageServiceClientMock.Verify(proxy => proxy.SubmitMessage(It.Is<XmlElement>(xmlElement => XMLDeserializer<QueueableMessage>.Deserialize(xmlElement).Messages.Contains(message))), Times.Once());
}

Au fait, comment puis-je extraire l'expression de l'appel Verify ?

298voto

Rich Tebb Points 2258

Si la logique de vérification n'est pas triviale, il sera compliqué d'écrire une grande méthode lambda (comme le montre votre exemple). Vous pourriez mettre toutes les déclarations de test dans une méthode séparée, mais je n'aime pas faire cela car cela perturbe le flux de lecture du code de test.

Une autre option est d'utiliser un callback sur l'appel Setup pour stocker la valeur qui a été passée dans la méthode fantaisie, puis d'écrire le standard Assert des méthodes pour le valider. Par exemple :

// Arrange
MyObject saveObject;
mock.Setup(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()))
        .Callback<int, MyObject>((i, obj) => saveObject = obj)
        .Returns("xyzzy");

// Act
// ...

// Assert
// Verify Method was called once only
mock.Verify(c => c.Method(It.IsAny<int>(), It.IsAny<MyObject>()), Times.Once());
// Assert about saveObject
Assert.That(saveObject.TheProperty, Is.EqualTo(2));

8 votes

Un grand avantage de cette approche est qu'elle vous donnera des échecs de test spécifiques pour la façon dont l'objet est incorrect (puisque vous testez chaque objet individuellement).

1 votes

Je pensais être le seul à faire ça, je suis content de voir que c'est une approche raisonnable !

3 votes

Je pense qu'il est préférable d'utiliser It.Is<MyObject>(validator) comme Mayo, car cela évite la façon un peu maladroite d'enregistrer la valeur du paramètre dans le cadre de la lambda.

141voto

Mayo Points 5532

J'ai vérifié les appels de la même manière - je pense que c'est la bonne façon de procéder.

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => mo.Id == 5 && mo.description == "test")
  ), Times.Once());

Si votre expression lambda devient difficile à manier, vous pouvez créer une fonction qui prend MyObject comme entrées et sorties true / false ...

mockSomething.Verify(ms => ms.Method(
    It.IsAny<int>(), 
    It.Is<MyObject>(mo => MyObjectFunc(mo))
  ), Times.Once());

private bool MyObjectFunc(MyObject myObject)
{
  return myObject.Id == 5 && myObject.description == "test";
}

De plus, soyez conscient d'un bug avec Mock où le message d'erreur indique que la méthode a été appelée plusieurs fois alors qu'elle n'a pas été appelée du tout. Il est possible qu'il ait été corrigé depuis - mais si vous voyez ce message, vous pouvez envisager de vérifier que la méthode a bien été appelée.

EDIT : Voici un exemple d'appel multiple de verify pour les scénarios où vous voulez vérifier que vous appelez une fonction pour chaque objet d'une liste (par exemple).

foreach (var item in myList)
  mockRepository.Verify(mr => mr.Update(
    It.Is<MyObject>(i => i.Id == item.Id && i.LastUpdated == item.LastUpdated),
    Times.Once());

Même approche pour la configuration...

foreach (var item in myList) {
  var stuff = ... // some result specific to the item
  this.mockRepository
    .Setup(mr => mr.GetStuff(item.itemId))
    .Returns(stuff);
}

Ainsi, chaque fois que GetStuff est appelé pour cet itemId, il renvoie des éléments spécifiques à cet item. Vous pouvez également utiliser une fonction qui prend l'itemId en entrée et renvoie des éléments.

this.mockRepository
    .Setup(mr => mr.GetStuff(It.IsAny<int>()))
    .Returns((int id) => SomeFunctionThatReturnsStuff(id));

Une autre méthode que j'ai vue sur un blog il y a quelque temps (Phil Haack peut-être ?) avait une configuration qui retournait une sorte d'objet dequeue - à chaque fois que la fonction était appelée, elle tirait un élément d'une file d'attente.

1 votes

Merci, ça me paraît logique. Ce que je n'arrive toujours pas à comprendre, c'est quand il faut spécifier les détails dans Setup ou Verify. C'est assez déroutant. Pour l'instant, j'autorise tout dans Setup et je spécifie les valeurs dans Verify.

0 votes

Comment pensez-vous que je puisse vérifier les messages lorsqu'il y a plusieurs appels ? Le client prend des messages et peut créer de multiples queueableMessages, qui se retrouveront dans de multiples appels et dans chacun de ces appels, je dois vérifier différents messages. J'ai encore du mal avec les tests unitaires en général, je ne suis pas encore très familier avec eux.

0 votes

Je ne pense pas qu'il y ait une solution miracle pour savoir comment s'y prendre. Il faut de la pratique et on commence à s'améliorer. Pour ma part, je ne spécifie les paramètres que lorsque j'ai quelque chose à quoi les comparer et lorsque je ne teste pas déjà ce paramètre dans un autre test. Quant aux appels multiples, il existe plusieurs approches. Pour configurer et vérifier une fonction qui est appelée plusieurs fois, j'appelle généralement setup ou verify (Times.Once()) pour chaque appel que j'attends - souvent avec une boucle for. Vous pouvez utiliser les paramètres spécifiques pour isoler chaque appel.

26voto

dmitry.sergeyev Points 249

Une façon plus simple serait de faire :

ObjectA.Verify(
    a => a.Execute(
        It.Is<Params>(p => p.Id == 7)
    )
);

1voto

ds4940 Points 1044

J'en ai eu un aussi, mais le paramètre de l'action était une interface sans propriétés publiques. J'ai fini par utiliser It.Is() avec une méthode séparée et, dans cette méthode, j'ai dû faire une simulation de l'interface.

public interface IQuery
{
    IQuery SetSomeFields(string info);
}

void DoSomeQuerying(Action<IQuery> queryThing);

mockedObject.Setup(m => m.DoSomeQuerying(It.Is<Action<IQuery>>(q => MyCheckingMethod(q)));

private bool MyCheckingMethod(Action<IQuery> queryAction)
{
    var mockQuery = new Mock<IQuery>();
    mockQuery.Setup(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition())
    queryAction.Invoke(mockQuery.Object);
    mockQuery.Verify(m => m.SetSomeFields(It.Is<string>(s => s.MeetsSomeCondition(), Times.Once)
    return true
}

1voto

Fernando Points 3097

Je pense que le problème réside dans le fait que Moq vérifie l'égalité. Et, puisque XmlElement ne surcharge pas Equals, son implémentation vérifiera l'égalité des références.

Ne pouvez-vous pas utiliser un objet personnalisé, pour pouvoir remplacer les égalités ?

0 votes

Oui, j'ai fini par le faire. Je me suis rendu compte que le problème était de vérifier le Xml. Dans la deuxième partie de la question, j'ai ajouté une réponse possible : désérialiser le xml vers un objet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X