Usando Moq para simular un método asincrónico para una prueba unitaria
Estoy probando un método para un servicio que realiza una API
llamada web. Usar normal HttpClient
funciona 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 IHttpClient
interfaz 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 HttpStatusResult
para 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.
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 IHttpClient
lugar de burlarte de todo; realmente depende de la frecuencia con la que la necesites.
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() { .. .. .. });
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());
Con el método Mock.Of<...>(...)
for async
puedes usar Task.FromResult(...)
:
var client = Mock.Of<IHttpClient>(c =>
c.PostAsync(It.IsAny<Uri>(), It.IsAny<HttpContent>()) == Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))
);