Utilizzo di Moq per deridere un metodo asincrono per un unit test


180

Sto testando un metodo per un servizio che effettua una APIchiamata Web . L'uso normale HttpClientfunziona bene per i test unitari se eseguo anche il servizio Web (situato in un altro progetto nella soluzione) localmente.

Tuttavia, quando eseguo il check-in delle mie modifiche, il server di compilazione non avrà accesso al servizio Web, pertanto i test falliranno.

Ho escogitato un modo per aggirare questo per i test delle mie unità creando IHttpClientun'interfaccia e implementando una versione che utilizzo nella mia applicazione. Per i test unitari, faccio una versione derisa completa di un metodo post asincrono deriso. Ecco dove ho riscontrato problemi. Voglio restituire un OK HttpStatusResultper questo particolare test. Per un altro test simile restituirò un risultato negativo.

Il test verrà eseguito ma non verrà mai completato. È in attesa. Sono nuovo nella programmazione asincrona, i delegati e lo stesso Moq e ho cercato SO e google per un po 'di tempo imparando cose nuove, ma non riesco ancora a superare questo problema.

Ecco il metodo che sto cercando di testare:

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
}

Ecco il mio metodo di test unitario:

[TestMethod]
public async Task QueueNotificationAsync_Completes_With_ValidEmail()
{
    Email email = new Email()
    {
        FromAddress = "bob@example.com",
        ToAddress = "bill@example.com",
        CCAddress = "brian@example.com",
        BCCAddress = "ben@example.com",
        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.");
}

Che cosa sto facendo di sbagliato?

Grazie per l'aiuto.

Risposte:


351

Stai creando un'attività ma non la avvii mai, quindi non viene mai completata. Tuttavia, non solo avviare l'attività, ma passa a utilizzare Task.FromResult<TResult>quale ti darà un'attività che è già stata completata:

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

Nota che non testerai l'asincronia reale in questo modo - se vuoi farlo, devi fare un po 'più di lavoro per creare un Task<T>che puoi controllare in modo più preciso ... ma questo è qualcosa per un altro giorno.

Potresti anche considerare di usare un falso IHttpClientpiuttosto che deridere tutto - dipende davvero da quanto spesso ne hai bisogno.


2
Grazie mille. Funzionava alla grande. Ho pensato che probabilmente era qualcosa di semplice che non capivo.
mvanella,

2
Ri: Falso IHttpClient, l'ho considerato, ma dovevo essere in grado di restituire diversi HttpStatusCodes per diversi test basati sul comportamento previsto che ritornava dall'API Web e questo mi sembrava dare più controllo.
mvanella,

3
@mvanella: Sì, quindi creeresti un falso che può restituire quello che vuoi. Solo qualcosa a cui pensare.
Jon Skeet,

134
Per chiunque lo trovi ora, Moq 4.2 ha un'estensione chiamata ReturnsAysnc, che fa esattamente questo.
Stuart Grassie,

3
@legacybass Non riesco a trovare un collegamento ad alcuna documentazione per questo, anche se i documenti API dicono che sono stati creati contro v4.2.1312.1622 che è stato rilasciato quasi esattamente un anno fa. Vedi questo commit che è stato fatto pochi giorni prima di quella versione.
Sul

17

Raccomanda la risposta di @Stuart Grassie sopra.

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

1

Con Mock.Of<...>(...)per il asyncmetodo puoi usare Task.FromResult(...):

var client = Mock.Of<IHttpClient>(c => 
    c.PostAsync(It.IsAny<Uri>(), It.IsAny<HttpContent>()) == Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))
);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.