it-swarm.com.de

Verwenden von Moq zum Verspotten einer asynchronen Methode für einen Komponententest

Ich teste eine Methode für einen Dienst, der einen Web API -Aufruf ausführt. Die Verwendung eines normalen HttpClient für Komponententests funktioniert einwandfrei, wenn ich den Webdienst (der sich in einem anderen Projekt in der Projektmappe befindet) auch lokal ausführe.

Wenn ich meine Änderungen einchecke, hat der Build-Server jedoch keinen Zugriff auf den Webdienst, sodass die Tests fehlschlagen.

Ich habe einen Weg gefunden, um dies für meine Unit-Tests zu umgehen, indem ich eine IHttpClient -Schnittstelle erstellt und eine Version implementiert habe, die ich in meiner Anwendung verwende. Für Unit-Tests erstelle ich eine verspottete Version mit einer verspotteten asynchronen Post-Methode. Hier bin ich auf Probleme gestoßen. Ich möchte für diesen bestimmten Test ein OK HttpStatusResult zurückgeben. Für einen anderen ähnlichen Test werde ich ein schlechtes Ergebnis zurückgeben.

Der Test wird ausgeführt, aber niemals abgeschlossen. Es hängt am Warten. Ich bin neu in der asynchronen Programmierung, bei Delegierten und in Moq selbst und habe eine Weile lang nach SO und google gesucht, um neue Dinge zu lernen, aber ich scheine immer noch nicht über dieses Problem hinwegzukommen.

Hier ist die Methode, die ich testen möchte:

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
}

Hier ist meine Unit-Test-Methode:

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

Was mache ich falsch?

Danke für deine Hilfe.

159
mvanella

Sie erstellen eine Aufgabe, starten sie jedoch nie, sodass sie niemals abgeschlossen wird. Starten Sie jedoch nicht nur die Aufgabe, sondern verwenden Sie stattdessen Task.FromResult<TResult> , um eine bereits erledigte Aufgabe zu erhalten:

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

Beachten Sie, dass Sie die tatsächliche Asynchronität nicht auf diese Weise testen werden. Wenn Sie dies tun möchten, müssen Sie etwas mehr Arbeit leisten, um einen Task<T> Zu erstellen, den Sie genauer steuern können. .. aber das ist etwas für einen anderen Tag.

Vielleicht möchten Sie auch eine Fälschung für IHttpClient verwenden, anstatt sich über alles lustig zu machen - das hängt wirklich davon ab, wie oft Sie es brauchen.

313
Jon Skeet

Empfehlen Sie die Antwort von @Stuart Grassie weiter.

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