web-dev-qa-db-fra.com

Quelle est la différence entre passer It.IsAny <int> () et la valeur de It.IsAny <int> () à une configuration de méthode

J'utilise Moq et je veux créer des classes de générateur pour créer mes mocks avec des valeurs par défaut raisonnables prédéfinies qui peuvent être remplacées pendant la configuration du test si nécessaire. L'approche que j'ai adoptée utilise des méthodes d'extension dans lesquelles je transmets les valeurs des paramètres d'entrée et la sortie attendue. Ce faisant, je vois un comportement différent dans ce qui me semble être un code sémantiquement équivalent: passer It.IsAny () directement dans une configuration vs passer la valeur de It.IsAny () indirectement dans une configuration. Exemple:

public interface IFoo
{
    bool Bar(int value);
    bool Bar2(int value);
}
public class Foo : IFoo
{
    public bool Bar(int value) { return false; }
    public bool Bar2(int value) { return false; }
}

var mock = new Mock<IFoo>();
mock.Setup(x => x.Bar(It.IsAny<int>())).Returns(true);
Assert.IsTrue(mock.Object.Bar(123));                    // Succeeds

var myValue = It.IsAny<int>();
mock.Setup(x => x.Bar2(myValue)).Returns(true);
Assert.IsTrue(mock.Object.Bar2(123));                   // Fails

Les deux appels sont équivalents (pour moi), mais l'appel à Bar2 échoue à l'assertion. Pourquoi est-ce?

27
Craig Boland

It.IsAny Ne permet à Moq de faire correspondre les futures invocations d'appels de méthode que s'il est utilisé dans la construction Setup. Lorsque Setup est appelé Moq, il suffit d'ajouter l'appel de méthode à son cache d'appels de méthode déjà configurés. Notez que l'argument de Setup dans votre exemple a le type Expression<Func<IFoo, bool>>. Étant donné que vous passez un Expression, l'appel de méthode réel n'est pas appelé et Moq a la possibilité de parcourir l'expression pour déterminer quels paramètres de l'appel de méthode étaient explicites et lesquels sont It.IsAny arguments. Il utilise cette capacité pour déterminer si un futur appel de méthode au moment de l'exécution correspond à l'un des appels de méthode déjà configurés.

Pour que la méthode Bar puisse accepter l'argument It.IsAny<int>(), il faut que It.IsAny<int>() retourne un int (puisque c'est le type du paramètre de Bar). En général, le type de retour de It.IsAny<T> Doit être T. Une valeur arbitraire de T doit être choisie. Le choix le plus naturel est default(T), qui fonctionne pour les types de référence et les types de valeur. (En savoir plus sur le mot clé par défaut ici ). Dans votre cas, c'est default(int), qui est 0.

Ainsi, lorsque vous évaluez réellement It.IsAny<int>(), la valeur de 0 Est immédiatement renvoyée. Cependant, lorsque vous utilisez It.IsAny<int>() dans un Expression (comme dans l'argument de la méthode Setup), l'arborescence de l'appel de méthode est conservée et Moq peut correspondre futurs appels de méthode à l'appel de méthode encapsulé par le Expression.

Ainsi, bien que vous ne puissiez pas conserver It.IsAny<int>() en tant que variable de manière significative, vous pouvez conserver l'intégralité de Expression dans une variable:

Expression<Func<IFoo, bool>> myExpr = x => x.Bar2(It.IsAny<int>());
mock.Setup(myExpr).Returns(true);
Assert.IsTrue(mock.Object.Bar2(123));  

Enfin, je veux juste vous rappeler que Moq est open source. La source est disponible ici . Je trouve utile d'avoir ce code source pour pouvoir cliquer et explorer le code et les tests unitaires.

39
Ben Reich

It.IsAny<int>() a un type de retour de int et retourne 0, donc votre deuxième configuration équivaut à:

mock.Setup(x => x.Bar2(0)).Returns(true);

Je n'ai pas vérifié le code moq, mais je suis presque sûr que lorsqu'il évalue l'expression dans la méthode de configuration, il prend en compte que le paramètre est en fait It.IsAny par rapport à un nombre normal.

Vous feriez mieux de créer les configurations directement dans vos méthodes d'assistance et de ne pas passer It.IsAny.

1
Sunny Milenov