web-dev-qa-db-fra.com

Moq - Est-il possible de spécifier dans une configuration les critères de vérification (par exemple les heures appelées)?

Si vous devez configurer une valeur de retour, ainsi que vérifier combien de fois l'expression a été appelée, pouvez-vous le faire en une seule instruction?

D'après ce que je peux comprendre, la fonction Setup(SomeExpression).Verifiable() de Moq a été appelée avec Verify(), en fait une fonction Verify(SomeExpression, Times.AtLeastOnce)? c'est-à-dire qu'il vérifie que l'expression a été appelée uniquement.

Voici un exemple pour mieux expliquer la question. Pour une interface:

interface IFoo
{
    int ReturnSomething();
}

Les deux blocs suivants sont-ils équivalents (à part le premier, vérifiera toutes les configurations marquées comme vérifiables)?

void Test()
{
    var mock = new Mock<IFoo>();
    mock.Setup((m) => m.ReturnSomething()).Returns(1).Verifiable();

    mock.Verify();
}

et

void Test()
{
    var mock = new Mock<IFoo>();
    mock.Setup((m) => m.ReturnSomething()).Returns(1);

    mock.Verify((m) => m.ReturnSomething(), Times.AtLeastOnce());
}

Si je voulais vérifier le nombre d'appels (disons deux fois), est-ce le seul moyen, où l'expression est répétée pour la configuration et la vérification?

void Test()
{
    var mock = new Mock<IFoo>();
    mock.Setup((m) => m.ReturnSomething()).Returns(1);

    mock.Verify((m) => m.ReturnSomething(), Times.Exactly(2));
}

Je n'aime tout simplement pas avoir à appeler Setup and Verify. Eh bien, puisque c'est une bonne idée pour AAA, pour reformuler, je n'aime pas avoir à répéter l'expression pour la configuration et la vérification. Pour le moment, je stocke l'expression dans une variable et la passe à chaque méthode, mais je ne me sens pas si propre.

PS - Le contexte pour cela est pour un test vérifiant quand un cache est mis à jour ou non (expirations etc.)

35
GregS

J'ai ce problème tout le temps. J'utilise des simulations strictes, et je veux spécifier strictement (c'est-à-dire que j'ai utilisé It.Is<>() au lieu de It.IsAny()) ainsi que vérifier strictement (c'est-à-dire en spécifiant Times). Vous ne pouvez malheureusement pas utiliser vérifiable pour cela, car il manque une surcharge de Verifiable(Times) à Moq.

L'expression complète de l'appel, y compris It.Is<>() est généralement grande. Donc, pour éviter les doublons, j'ai généralement recours aux éléments suivants:

Expression<Action<MockedType>> expression = mockedTypeInstance => mockedTypeInstance.MockedMethod(It.Is<TFirstArgument>(firstArgument => <some complex statement>)/*, ...*/);
_mock.Setup(expression);

/* run the test*/

_mock.Verify(expression, Times.Once);

Pas très lisible, mais je ne pense pas qu'il existe une autre façon d'utiliser à la fois une configuration stricte et une vérification stricte.

17
Evren Kuzucuoglu

Pour répondre à la première question, oui les deux blocs sont équivalents. Les deux échoueront lorsque .Verify Sera appelé si la méthode sur la maquette n'a pas été appelée.

Vous ne pouvez pas spécifier la vérification à l'avance pour autant que je sache et si vous y pensez, cela a du sens.

Ceci spécifie le comportement de la maquette:

mock.Setup(m => m.ReturnSomething()).Returns(1);

Ceci vérifie le comportement de l'appelant:

mock.Verify(m => m.ReturnSomething(), Times.AtLeastOnce());

Personnellement, je préfère appeler vérification individuelle pour confirmer le comportement requis de l'appelant, les .Verifiable() et .Verify() sont des raccourcis moins stricts (ils vérifient simplement que la méthode a été appelée une ou plusieurs fois) cependant, si vous savez que votre code ne doit appeler une méthode qu'une seule fois, mettez la vérification à la fin pour la confirmer.

J'ai commencé à le faire après qu'une fusion de code ait abouti à une méthode appelée deux fois, le test passait toujours car il était appelé au moins une fois, mais cela signifiait également que quelque chose d'autre s'était produit plusieurs fois, ce qui n'aurait pas dû!

16
Trevor Pilley

Expliquant la réponse d'Evren Kuzucuoglu, j'ai créé les méthodes d'extension suivantes pour rendre la création des expressions un peu plus simple:

/// <summary>
/// Creates a method call expression that can be passed to both <see cref="Setup"/> and <see cref="Verify"/>.
/// </summary>
/// <typeparam name="T">Mocked object type.</typeparam>
/// <param name="mock">Mock of <see cref="T"/>.</param>
/// <param name="expression">Method call expression to record.</param>
/// <returns>Method call expression.</returns>
public static Expression<Action<T>> CallTo<T>(this Mock<T> mock, Expression<Action<T>> expression) where T : class
{
    return expression;
}

/// <summary>
/// Creates a method call expression that can be passed to both <see cref="Setup"/> and <see cref="Verify"/>.
/// </summary>
/// <typeparam name="T">Mocked object type.</typeparam>
/// <typeparam name="TResult">Method call return type.</typeparam>
/// <param name="mock">Mock of <see cref="T"/>.</param>
/// <param name="expression">Method call expression to record.</param>
/// <returns>Method call expression.</returns>
public static Expression<Func<T, TResult>> CallTo<T, TResult>(this Mock<T> mock, Expression<Func<T, TResult>> expression) where T : class
{
    return expression;
}

Exemple d'utilisation:

var createMapperCall = mockMappingFactory.CallTo(x => x.CreateMapper());
mockMappingFactory.Setup(createMapperCall).Returns(mockMapper.Object);

mockMappingFactory.Verify(createMapperCall, Times.Once());
5
Jack A.