web-dev-qa-db-fra.com

Utiliser Moq pour simuler une méthode asynchrone pour un test unitaire

Je teste une méthode pour un service qui effectue un appel Web API. L'utilisation d'un HttpClient normal fonctionne correctement pour les tests unitaires si j'exécute également le service Web (situé dans un autre projet de la solution) localement.

Toutefois, lorsque j'archive mes modifications, le serveur de génération n'a pas accès au service Web et les tests échouent.

J'ai conçu un moyen de contourner ce problème pour mes tests unitaires en créant une interface IHttpClient et en implémentant une version que j'utilise dans mon application. Pour les tests unitaires, je réalise une version simulée complète avec une méthode de post asynchrone simulée. Voici où j'ai eu des problèmes. Je veux retourner un OK HttpStatusResult pour ce test particulier. Pour un autre test similaire, je vais retourner un mauvais résultat.

Le test se déroulera mais ne sera jamais terminé. Il est suspendu à l'attente. Je suis nouveau dans le domaine de la programmation asynchrone, des délégués et de Moq lui-même et je cherche depuis un moment SO et google, mais je n'arrive toujours pas à surmonter ce problème.

Voici la méthode que j'essaie de tester:

public async Task<bool> QueueNotificationAsync(IHttpClient client, Email email)
{
    // do stuff
    try
    {
        // The test hangs here, never returning
        HttpResponseMessage response = await client.PostAsync(uri, content);

        // more logic here
    }
    // more stuff
}

Voici ma méthode de test unitaire:

[TestMethod]
public async Task QueueNotificationAsync_Completes_With_ValidEmail()
{
    Email email = new Email()
    {
        FromAddress = "[email protected]",
        ToAddress = "[email protected]",
        CCAddress = "[email protected]",
        BCCAddress = "[email protected]",
        Subject = "Hello",
        Body = "Hello World."
    };
    var mockClient = new Mock<IHttpClient>();
    mockClient.Setup(c => c.PostAsync(
        It.IsAny<Uri>(),
        It.IsAny<HttpContent>()
        )).Returns(() => new Task<HttpResponseMessage>(() => new HttpResponseMessage(System.Net.HttpStatusCode.OK)));

    bool result = await _notificationRequestService.QueueNotificationAsync(mockClient.Object, email);

    Assert.IsTrue(result, "Queue failed.");
}

Qu'est-ce que je fais mal?

Merci de votre aide.

159
mvanella

Vous créez une tâche mais ne la démarrez jamais, alors elle ne se termine jamais. Cependant, ne vous contentez pas de démarrer la tâche, utilisez plutôt Task.FromResult<TResult> , ce qui vous donnera une tâche déjà terminée:

_...
.Returns(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)));
_

Notez que vous ne testerez pas l'asynchronie réelle de cette manière. Si vous souhaitez le faire, vous devez effectuer un peu plus de travail pour créer un _Task<T>_ que vous pouvez contrôler de manière plus fine. Mais c'est quelque chose pour un autre jour.

Vous pouvez également envisager d'utiliser un faux pour IHttpClient plutôt que de vous moquer de tout - cela dépend vraiment de la fréquence à laquelle vous en avez besoin.

313
Jon Skeet

Recommandez la réponse de @Stuart Grassie ci-dessus.

var moqCredentialMananger = new Mock<ICredentialManager>();
moqCredentialMananger
                    .Setup(x => x.GetCredentialsAsync(It.IsAny<string>()))
                    .ReturnsAsync(new Credentials() { .. .. .. });
1
DineshNS