¿Usas Microsoft.AspNetCore.TestHost? Si la respuesta es si, seguramente (espero) lo que te contaré en este post te resulte útil. Por el contrario, si aún no conoces como y para que usar esta librería este post igual te ayuda a descubrir una nueva forma de testar tus HTPP API.
Para los que conozcáis TestServer, incluida dentro de Microsoft.AspNetCore.TestHost, sabréis que nos ofrece una manera de testar nuestros proyectos de HTTP API de una forma simple y muy poderosa. La idea subyacente es ofrecernos un cliente HttpClient que podremos utilizar para llamar a nuestro API sin necesidad de que este sea alojado en ningún servidor web.
Introducción
Para ilustrar este trabajo supongamos que partimos de la plantilla por defecto de ASP.NET Core para API HTTP.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[Route("api/values")] public class ValuesController : ControllerBase { [Authorize] [HttpGet] public IActionResult Values() { return Ok(new[] { "Value1", "Value2" }); } [HttpGet("public")] public IActionResult PublicValues() { return Ok(new[] { "Value1", "Value2" }); } } |
Probar este controlador tal cual seria llamado por un cliente usando HTTP se vuelve insultantemente simple, tan solo tendremos que configurar nuestro TestServer y realizar las llamadas.
1 2 3 4 5 6 7 8 9 |
// Build the test server var host = new WebHostBuilder() .UseStartup<Startup>(); var server = new TestServer(host); //create the client var userHttpCient = _server.CreateClient() |
Del fragmento anterior hay un elemento importante que mencionar. Para crear nuestro TestServer hemos utilizado un IWebHost construido con una clase de Startup, la cual podría ser tranquilamente la misma que usamos en el proyecto para definir nuestro HTTP API aunque esto, para ilustrar nuestro post sirve, en realidad, suele ser mejor idea que el propio proyecto de test pueda definir su clase de startup.
Separar el alojamiento de tus proyectos de API HTTP siempre es una buena idea, y no solamente porque nos permitirá en nuestros proyecto de test quitarnos ruido que solo afecta al alojamiento.
Ahora que ya tenemos nuestro HttpClient ya podremos utilizarlo para hacer las llamadas a nuestro controlador.
1 2 3 |
var response = await userHttpClient.GetAsync("api/values"); response.EnsureHttpSucessCode(); |
En este post solamente nos estamos centrando en presentar de forma ligera TestServer. En futuras entradas veremos como es recomendable reutilizar para nuestros diferentes tests esta instancia y como podremos hacerlo con ClassFixtures / CollectionFixtures, en el caso de xunit, así como otros elementos habituales en este tipo de Tests, pero que se escapan aquí al propósito de esta entrada.
¿Porqué usar Acheve.TestHost?
¿Para que crear una librería si TestServer es ya tan potente? Pues bien, porque hay ciertos escenarios habituales que nos puede resultar muy útil y nos librará de mucho código extra que hacer. A continuación presentaremos dos puntos donde nos ofrecerá mejoras apreciables.
Seguridad
Por regla general, nuestras HTTP API están securizadas y la ejecución de las acciones requerirá que el usuario esté autorizado. De hecho, en nuestro ejemplo hemos puesto el atributo Authorization por el cual solamente los usuarios autenticados podrán ejecutar las acciones del controlador ValuesController.
Nuestro host es quien elige como autenticamos estas llamadas, así por ejemplo en nuestro caso podríamos tener lo siguiente:
1 2 |
services.AddAuthentication() .AddJwtBearer(); |
Si no hemos separado las clases de Startup de nuestro alojamiento de la clase Startup usada al construir TestServer, tendremos que lidiar en como conseguir estos tokens JWT para hacer nuestras llamadas con las clase HttpClient conseguida anteriormente. Sin embargo, si separamos ambos mundos, en nuestro alojamiento podríamos tener el código anterior y en nuestros tests podríamos cambiarlo para hacernos la vida mas sencilla. Achve.TestHost nos permite que podamos configurar nuestra clase Startup de test con un mecanismo propio de autenticación que nos simplificará mucho las pruebas en nuestros tests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class TestStartup { public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(options => { options.DefaultScheme = TestServerAuthenticationDefaults.AuthenticationScheme; }) .AddTestServerAuthentication(); var mvcCoreBuilder = services.AddMvcCore(); ApiConfiguration.ConfigureCoreMvc(mvcCoreBuilder); } public void Configure(IApplicationBuilder app) { app.UseAuthentication(); app.UseMvcWithDefaultRoute(); } } |
El método AddTestServerAuthentication nos habilita que las llamadas que hagamos con nuestro HttpClient estén autenticadas y además que en los tests podamos establecer fácilmente las claims que necesitemos para cada prueba.
Como podemos ver en el siguiente fragmento.
1 2 3 4 5 6 |
userHttpClient.CreateRequest("api/values") .WithIdentity(new List<Claim>() { new Claim(ClaimTypes.Name,"unai"), new Claim(ClaimTypes.Role,"admin") }).GetAsync()).StatusCode.Should().Be(HttpStatusCode.OK); |
En pocas palabras diríamos que AddTestServerAuthentication nos habilita un nuevo middleware sobre el que después podremos establecer un conjunto de claims que serán establecidas en el contexto del usuario para cada una de las pruebas que hagamos. En concreto, el método WithIdentity es el que nos permitirá establecer estas claims a utilizar para cada uno de nuestros tests.
Uris
Otro de los elementos con los que solemos lidiar en los tests con TestServer es el manejo de estas magic strings que representan las uris de las rutas a las que llamamos, como en el caso anterior api/values. Las alternativas habituales son varias, como crear una pequeña clase estática en la que definamos estas rutas y que sea fácilmente modificable y/o parametrizable como por ejemplo la siguiente.
1 2 3 4 5 6 7 8 9 10 |
public static class Api { public static class Get { public static string Hello(string name) { return $"api/hello?name={name}"; } } } |
El problema de esta aproximación, aunque también tiene sus ventajas está en el hecho de que precisamente son magic strings, que representan unas rutas que en el día a día pueden ser alteradas y que romperán muchos tests por el mero hecho de una cambio de nombre, un nuevo segmento de ruta etc. Acheve.TestHost nos ofrece un nuevo método extensor a TestServer, CreateHttpApiRequest, que nos permitirá devolvernos un RequestBuilder configurado con una ruta construída con una expresión lambda de la llamada al controlador. En el siguiente fragmento podemos ver un sencillo ejemplo:
1 2 3 4 5 6 |
testServer.CreateHttpApiRequest<HelloController>(c=>c.Get("unai")) //api/hello?name=unai .WithIdentity(new List<Claim>() { new Claim(ClaimTypes.Name,"unai"), new Claim(ClaimTypes.Role,"user") }).GetAsync()).StatusCode.Should().Be(HttpStatusCode.OK); |
En estos momentos CreateHttpApiRequest solamente es capaz de entender rutas construídas con AttributeRouting y no mediante la definición de mapa de rutas y/o otras convenciones. En nuestro caso suponemos que AttributeRouting es la norma por defecto por ser la más flexible de todas.
Resúmen
Acheve.TestHost es un proyecto de Xabaril, en el que he tenido la suerte de participar junto con el impulsor y padre del mismo Hugo Biarge @hbiarge.
https://github.com/Xabaril/Acheve.TestHost
https://github.com/Xabaril
Aunque hay mucho que profundizar, en esta entrada hemos visto que es TestServer y porqué Acheve.TestHost nos aporta ciertas funcionalidades que nos ayudarán en su uso.
Si quiere ver algún ejemplo de uso de Acheve.TestHost aquí.
Si quiere aprender más las posibilidades de CreateHttpApiRequest aquí.
Si quiere revisar la WiKi del proyecto aquí.