unitarias - unit test c#
Aserción de excepción de prueba asíncrona Nunit (5)
Estás viendo problemas debido a async void
.
En particular:
1) async () => await userController.Get("foo")
se convierte en TestDelegate
, que devuelve void
, por lo que su expresión lambda se trata como async void
. Entonces el corredor de prueba comenzará a ejecutar el lambda pero no esperará a que se complete. La lambda vuelve antes de que se complete (porque es async
), y el corredor de prueba ve que se devolvió sin excepción.
2) Wait
envuelve cualquier excepción en una AggregateException
.
3) Una vez más, la async
lambda está siendo tratada como async void
, por lo que el corredor de prueba no está esperando su finalización.
4) Te recomiendo que hagas esta async Task
lugar de async void
, pero en este caso el corredor de prueba espera a que se complete y, por lo tanto, ve la excepción.
De acuerdo con este informe de error , hay una solución para esto en la próxima compilación de NUnit. Mientras tanto, puedes construir tu propio método ThrowsAsync
; un ejemplo para xUnit está aquí .
Tengo un controlador UserController
con esta acción
// GET /blah
public Task<User> Get(string domainUserName)
{
if (string.IsNullOrEmpty(domainUserName))
{
throw new ArgumentException("No username specified.");
}
return Task.Factory.StartNew(
() =>
{
var user = userRepository.GetByUserName(domainUserName);
if (user != null)
{
return user;
}
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, string.Format("{0} - username does not exist", domainUserName)));
});
}
Estoy intentando escribir una prueba para el caso en el que lanzo una excepción 404.
Esto es lo que he intentado, con la salida -
1)
[Test]
public void someTest()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
Assert.That(async () => await userController.Get("foo"), Throws.InstanceOf<HttpResponseException>());
}
Prueba de resultado fallida
Expected: instance of <System.Web.Http.HttpResponseException>
But was: no exception thrown
2)
[Test]
public void someTest()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
var httpResponseException = Assert.Throws<HttpResponseException>(() => userController.Get("foo").Wait());
Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
Prueba de resultado fallida
Expected: <System.Web.Http.HttpResponseException>
But was: <System.AggregateException> (One or more errors occurred.)
3)
[Test]
public void someTest()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
var httpResponseException = Assert.Throws<HttpResponseException>(async () => await userController.Get("foo"));
Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
Prueba de resultado fallida
Expected: <System.Web.Http.HttpResponseException>
But was: null
4)
[Test]
[ExpectedException(typeof(HttpResponseException))]
public async void ShouldThrow404WhenNotFound()
{ var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
var task = await userController.Get("foo");
}
Resultado Pases de prueba
Preguntas -
- ¿Por qué Assert.Throws no maneja HttpResponseException, cuando ExpectedException lo hace?
- No quiero simplemente probar que se arrojó una excepción. Quiero afirmar en el Código de estado de la respuesta. ¿Cuál es la manera de hacer esto?
¡Cualquier comparación sobre estos comportamientos y su (s) causa (s) sería genial!
No estoy seguro de cuándo se agregó, pero la versión actual de Nunit (3.4.1 al momento de escribir) incluye un método ThrowsAsync
ver https://github.com/nunit/docs/wiki/Assert.ThrowsAsync
Ejemplo:
[Test]
public void ShouldThrow404WhenNotFound()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
var exception = Assert.ThrowsAsync<HttpResponseException>(() => userController.Get("foo"));
Assert.That(exception.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
Si espera una tarea, las excepciones que se lanzan se agregan en AggregateException. Puede inspeccionar las excepciones internas de AggregateException. Esta podría ser la razón por la cual su caso 2 no funciona.
Las excepciones no administradas generadas por el código de usuario que se ejecuta dentro de una tarea se propagan nuevamente a la cadena de unión, excepto en ciertos escenarios que se describen más adelante en este tema. Las excepciones se propagan cuando utiliza uno de los métodos estáticos o Task.Wait o Task.Wait estáticos o de instancia, y los maneja al encerrar la llamada en una declaración try-catch. Si una tarea es la principal de las tareas secundarias adjuntas, o si está esperando en varias tareas, pueden lanzarse múltiples excepciones. Para propagar todas las excepciones al hilo de llamada, la infraestructura de tarea las envuelve en una instancia de Excepción de agregación. La AggregateException tiene una propiedad InnerExceptions que se puede enumerar para examinar todas las excepciones originales que se lanzaron, y manejar (o no manejar) cada una individualmente. Incluso si solo se lanza una excepción, todavía está envuelto en una AggregateException.
Tengo un problema similar al que tienes en el escenario 3 Error de caso de prueba debido al siguiente resultado
Expected: <UserDefineException>
But was: null
mediante el uso del problema Assert.ThrowAsync <> se resuelve
El método de acción de mi API web y el método del caso de prueba de unidad como se muestra a continuación
public async Task<IHttpActionResult> ActionMethod(RequestModel requestModel)
{
throw UserDefineException();
}
[Test]
public void Test_Contrller_Method()
{
Assert.ThrowsAsync<UserDefineException>(() => _controller.ActionMethod(new RequestModel()));
}
Este blog habla de problemas similares a los míos.
Seguí la recomendación propuesta allí, y tengo una prueba como esta:
[Test]
public void ShouldThrow404WhenNotFound()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
var aggregateException = Assert.Throws<AggregateException>(() => userController.Get("foo").Wait());
var httpResponseException = aggregateException.InnerExceptions
.FirstOrDefault(x => x.GetType() == typeof(HttpResponseException)) as HttpResponseException;
Assert.That(httpResponseException, Is.Not.Null);
Assert.That(httpResponseException.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
}
No estoy muy satisfecho con esto, pero esto funciona.
EDIT 1
Inspirado por @StephenCleary, agregué una clase de ayuda estática que hace las afirmaciones que estoy buscando. Se parece a esto -
public static class AssertEx
{
public static async Task ThrowsAsync<TException>(Func<Task> func) where TException : class
{
await ThrowsAsync<TException>(func, exception => { });
}
public static async Task ThrowsAsync<TException>(Func<Task> func, Action<TException> action) where TException : class
{
var exception = default(TException);
var expected = typeof(TException);
Type actual = null;
try
{
await func();
}
catch (Exception e)
{
exception = e as TException;
actual = e.GetType();
}
Assert.AreEqual(expected, actual);
action(exception);
}
}
Ahora puedo tener una prueba como -
[Test]
public async void ShouldThrow404WhenNotFound()
{
var mockUserRepository = new Mock<IUserRepository>();
mockUserRepository.Setup(x => x.GetByUserName(It.IsAny<string>())).Returns(default(User));
var userController = new UserController(mockUserRepository.Object) { Request = new HttpRequestMessage() };
Action<HttpResponseException> asserts = exception => Assert.That(exception.Response.StatusCode, Is.EqualTo(HttpStatusCode.NotFound));
await AssertEx.ThrowsAsync(() => userController.Get("foo"), asserts);
}