Pouvez-vous m'aider à comprendre Moq Callback?

95

Utilisation de Moq et regardé Callbackmais je n'ai pas été en mesure de trouver un exemple simple pour comprendre comment l'utiliser.

Avez-vous un petit extrait de code de travail qui explique clairement comment et quand l'utiliser?

user9969
la source

Réponses:

83

Difficile à battre https://github.com/Moq/moq4/wiki/Quickstart

Si ce n'est pas assez clair, j'appellerais cela un bug de la documentation ...

EDIT: En réponse à votre clarification ...

Pour chaque méthode simulée que Setupvous effectuez, vous devez indiquer des éléments tels que:

  • contraintes sur les intrants
  • la valeur de / manière dont la valeur de retour (s'il y en a une) doit être dérivée

Le .Callbackmécanisme dit "Je ne peux pas le décrire pour le moment, mais quand un appel en forme de tel se produit, rappelez-moi et je ferai ce qui doit être fait". Dans le cadre de la même chaîne d'appels fluides, vous pouvez contrôler le résultat à retourner (le cas échéant) via .Returns". Dans les exemples QS, un exemple est qu'ils font augmenter la valeur renvoyée à chaque fois.

En général, vous n'aurez pas besoin d'un mécanisme comme celui-ci très souvent (les modèles de test xUnit ont des termes pour les antipatterns de la logique conditionnelle dans les tests), et s'il existe un moyen plus simple ou intégré d'établir ce dont vous avez besoin, il devrait être utilisé de préférence.

La partie 3 sur 4 de la série Moq de Justin Etheredge le couvre, et il y a un autre exemple de rappels ici

Un exemple simple de rappel peut être trouvé à Utilisation des rappels avec la publication Moq .

Ruben Bartelink
la source
3
Salut Ruben J'apprends Moq et si vous le souhaitez, je donne beaucoup d'exemples pour comprendre comment faire les choses en l'utilisant. Mon problème est que je ne comprends pas quand l'utiliser. Une fois que je comprends ce problème résolu, j'écrirai mon propre code. Si vous deviez l'expliquer dans votre propre mot, quand utiliseriez-vous le rappel? merci d'apprécier votre temps
user9969
15
Difficile à battre [lien]? Pas du tout. Ce lien vous montre comment faire des dizaines de choses différentes, mais ne vous dit pas pourquoi vous devriez faire l'une d'entre elles. Ce qui est un problème courant dans la documentation moqueuse, j'ai trouvé. Je peux compter sur zéro doigt le nombre de bonnes et claires explications de moquerie TDD + que j'ai trouvées. La plupart supposent un niveau de connaissance qui, si je l'avais, je n'aurais pas besoin de lire l'article.
Ryan Lundy
@Kyralessa: Je comprends votre point de vue. Personnellement, j'avais pas mal de connaissances en livres, j'ai donc trouvé le truc de démarrage rapide absolument parfait. Malheureusement, je ne connais pas de meilleur exemple que ceux auxquels j'ai lié à la fin de l'article. Si vous en trouvez un, postez-le ici et je serai heureux de le modifier (ou n'hésitez pas à le bricoler)
Ruben Bartelink
"Je vais faire ce qui doit être fait et vous indiquer le résultat à renvoyer (le cas échéant)" Je pense que c'est trompeur, AFAIU Callbackn'a rien à voir avec la valeur de retour (à moins que vous ne la liez par le code). Fondamentalement, il s'assure que le rappel est appelé avant ou après chaque appel (selon que vous l'avez enchaîné avant ou après Returnsrespectivement), clair et simple.
Ohad Schneider
1
@OhadSchneider Suite à mon lien ... vous avez raison! Je me demande (mais pas vraiment assez intéressé car je n'ai pas utilisé Moq depuis très longtemps) si l'interface Fluent a changé (cela ne semble pas probable, c'est-à-dire que j'ai fait une supposition erronée et n'ai pas lu la chose à laquelle j'ai lié comme je le travaillerais normalement de l'auto-complétion moi-même). J'espère que le correctif répond à votre point, faites-moi savoir si ce n'est pas le cas
Ruben Bartelink
59

Voici un exemple d'utilisation d'un rappel pour tester une entité envoyée à un service de données qui gère une insertion.

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

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

Syntaxe de méthode générique alternative:

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

Ensuite, vous pouvez tester quelque chose comme

Assert.AreEqual("test", insertedEntity.Description, "Wrong Description");
Jeff Hall
la source
4
Sans doute pour ce cas particulier (selon que vous essayez d'exprimer des tests par rapport à un état ou à un comportement), il peut dans certains cas être plus propre d'utiliser un It.Is<T>dans un Mock.Verifyau lieu de joncher le test avec des températures. Mais +1 parce que je parie qu'il y a beaucoup de gens qui fonctionneront le mieux à partir d'un exemple.
Ruben Bartelink
10

Il existe deux types de CallbackMoq. L'une se produit avant le retour de l'appel; l'autre se produit après le retour de l'appel.

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

Dans les deux rappels, nous pouvons:

  1. inspecter les arguments de méthode
  2. arguments de méthode de capture
  3. changer l'état contextuel
Shaun Luttin
la source
2
En fait, les deux se produisent avant le retour de l'appel (en ce qui concerne l'appelant). Voir stackoverflow.com/a/28727099/67824 .
Ohad Schneider
5

Callbackest simplement un moyen d'exécuter le code personnalisé de votre choix lorsqu'un appel est effectué vers l'une des méthodes du simulacre. Voici un exemple simple:

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

J'ai récemment rencontré un cas d'utilisation intéressant pour cela. Supposons que vous attendiez des appels à votre maquette, mais qu'ils se produisent simultanément. Vous n'avez donc aucun moyen de connaître l'ordre dans lequel ils seront appelés, mais vous voulez savoir que les appels que vous attendiez ont eu lieu (quel que soit l'ordre). Vous pouvez faire quelque chose comme ceci:

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

BTW ne vous laissez pas confondre par la distinction trompeuse «avant Returns» et «après Returns». Il s'agit simplement d'une distinction technique pour savoir si votre code personnalisé s'exécutera après Returnsavoir été évalué ou avant. Aux yeux de l'appelant, les deux s'exécuteront avant que la valeur ne soit renvoyée. En effet, si la méthode est de voidretour, vous ne pouvez même pas appeler Returnset pourtant cela fonctionne de la même manière. Pour plus d'informations, consultez https://stackoverflow.com/a/28727099/67824 .

Ohad Schneider
la source
1

En plus des autres bonnes réponses ici, je l'ai utilisé pour exécuter la logique avant de lancer une exception. Par exemple, j'avais besoin de stocker tous les objets passés à une méthode pour une vérification ultérieure, et cette méthode (dans certains cas de test) avait besoin de lever une exception. L'appel .Throws(...)sur Mock.Setup(...)remplace l' Callback()action et ne l'appelle jamais. Cependant, en lançant une exception dans le callback, vous pouvez toujours faire toutes les bonnes choses qu'un rappel a à offrir, et toujours lever une exception.

Frank Bryce
la source