Usando Moq para simular un método asincrónico para una prueba unitaria

Resuelto mvanella asked hace 10 años • 4 respuestas

Estoy probando un método para un servicio que realiza una APIllamada web. Usar normal HttpClientfunciona bien para pruebas unitarias si también ejecuto el servicio web (ubicado en otro proyecto de la solución) localmente.

Sin embargo, cuando reviso mis cambios, el servidor de compilación no tendrá acceso al servicio web, por lo que las pruebas fallarán.

Ideé una forma de solucionar esto para mis pruebas unitarias creando una IHttpClientinterfaz e implementando una versión que uso en mi aplicación. Para las pruebas unitarias, hago una versión simulada completa con un método de publicación asincrónico simulado. Aquí es donde me he encontrado con problemas. Quiero devolver un OK HttpStatusResultpara esta prueba en particular. Para otra prueba similar daré un mal resultado.

La prueba se ejecutará pero nunca se completará. Se cuelga en espera. Soy nuevo en la programación asincrónica, los delegados y el propio Moq y he estado buscando en SO y en Google durante un tiempo aprendiendo cosas nuevas, pero todavía parece que no puedo superar este problema.

Este es el método que estoy intentando probar:

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
}

Aquí está mi método de prueba unitaria:

[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é estoy haciendo mal?

Gracias por su ayuda.

mvanella avatar Dec 31 '13 22:12 mvanella
Aceptado

Estás creando una tarea pero nunca la inicias, por lo que nunca la completas. Sin embargo, no simplemente comience la tarea; en su lugar, cambie a usar, Task.FromResult<TResult>lo que le dará una tarea que ya se ha completado:

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

Tenga en cuenta que no probará la asincronía real de esta manera; si desea hacerlo, necesita trabajar un poco más para crear una Task<T>que pueda controlar de una manera más detallada... pero eso es algo para otro día.

También es posible que desees considerar usar una falsificación en IHttpClientlugar de burlarte de todo; realmente depende de la frecuencia con la que la necesites.

Jon Skeet avatar Dec 31 '2013 15:12 Jon Skeet

Recomiende el comentario de @Stuart Grassie. Utilice Moq's ReturnsAsync.

var moqCredentialMananger = new Mock<ICredentialManager>();
moqCredentialMananger
                    .Setup(x => x.GetCredentialsAsync(It.IsAny<string>()))
                    .ReturnsAsync(new Credentials() { .. .. .. });
DineshNS avatar Aug 07 '2019 01:08 DineshNS

Intenta usar ReturnsAsync. En métodos asincrónicos funciona, creo que la base para resolver su problema debería ser similar.

_mocker.GetMock<IMyRepository>()
     .Setup(x => x.GetAll())
     .ReturnsAsync(_myFakeListRepository.GetAll());
Cleber Spirlandeli avatar Apr 12 '2022 02:04 Cleber Spirlandeli

Con el método Mock.Of<...>(...)for asyncpuedes usar Task.FromResult(...):

var client = Mock.Of<IHttpClient>(c => 
    c.PostAsync(It.IsAny<Uri>(), It.IsAny<HttpContent>()) == Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))
);
Andrii Viazovskyi avatar Sep 17 '2019 13:09 Andrii Viazovskyi