WebApi: Tests de integración con diferentes identidades

tldr;
Acabo de publicar un pequeño paquete de NuGet para facilitar los test de integración de WebApi cuando incluyen peticiones autenticadas y autorización por claims.
Lo puedes encontrar aqui: Acheve.Owin.Testing.Security
Y el código fuente aquí: Github

 

En general, no me gusta hacer test unitarios de mis controladores de WebApi. Me da la sensación que me aportan muy poco valor. Hay que mockear un montón de infraestructura, haciendo que los tests sean farragosos de configurar (por muchos métodos de extensión que creemos para facilitarnos el trabajo).

Una de las soluciones que más me gusta en estos escenarios es la de hacer test de integración del api. Juanma (aka @gulnor) lo comentaba por aquí (post de lectura muy recomendada) hace un tiempo, y yo no puedo estar más de acuerdo con esta visión.

“La complejidad reside en la configuración de los componentes”

El componente que suelo utilizar para estos test de integración es el paquete de NuGet Microsoft.Owin.Testing. Básicamente me ofrece un host basado en Owin para el api. Muy sencillo de configurar con su clase Startup y muy rápido de ejecutar.

Pero el problema es cuando el api, no sólo requiere peticiones autenticadas, sino que además incluye algún mecanismo de autorización basado en roles claims y hay determinados endpoints que tienen un comportamiento u otro en base a esta información. Desde luego, este es el escenario perfecto en el que los test de integración aportan muchísimo. Pero no es fácil de configurar. ¿Cómo puedo incluir de forma sencilla en mis test de integración los claims del usuario que quiero suplantar al realizar la petición?

La verdad es que he utilizado diferentes aproximaciones en diferentes proyectos, pero aquí te voy a contar la que más me ha gustado.

¿Dónde configuro el mecanismo de autenticación de mi api?

Antes de entrar en faena, déjame que haga una pequeña reflexión sobre este punto. Creo que es importante.

Con WebApi voy a poder “hostear” mi api de diferentes maneras. El propio framework ya permitía el concepto de Self Host cuando se publicó su versión 1. Esto quiere decir que no necesito un IIS para exponer el api, sino que lo puedo hacer en cualquier tipo de aplicación: consola, servicio de Windows, etc. Además, con la aparición de OWIN y “katana”, es todavía más fácil utilizar diferentes “hosts” para el api.

La idea es que mi api debe incluir la lógica de autorización, para lo que seguramente, necesitará un usuario autenticado para poder saber quién es y que permisos tiene sobre el api. Sobre todo en el escenario que planteábamos en el que hay que discriminar si el usuario que hace la petición está autorizado o le tenemos que devolver un 401 o un 403.

Pero en realidad es el host el que debe decidir qué mecanismo concreto de autenticación  se ha de utilizar. Es decir, qué elemento de la petición Http se debe utilizar para identificar al usuario y conocer cuales son sus credenciales (en el sentido de claims). En un host podré utilizar tokens bearer en la cabecera estándar “Authorization”, en otro una cabecera personalizada, en otro autenticación integrada de Windows o en otro autenticación básica. O incluso una combinación de ellas. Pero en cualquier caso, mi api, una vez autenticado el usuario por el mecanismo que considere el host debería responder de forma consistente a los requisitos de autorización.

Creando un middleware de autenticación personalizado

Por lo tanto, la aproximación está clara. El TestServer que me ofrece el paquete de NuGet Microsoft.Owin.Testing  no es más que un host de mi api en el que voy a configurar un mecanismo de autenticación personalizado que me permita, de forma sencilla, establecer las credenciales del usuario que quiero suplantar en la petición.

No voy a entrar en este momento en los detalles de cómo implementar un middleware de autenticación (lo dejamos para otro post), pero no es complicado. Básicamente nos vale con saber que, para el escenario de los test de integración, la información de los claims del usuario va a viajar codificada en Base64 en la cabecera estandar de autorización. Nada complicado. Pero cuidado. Nada seguro tampoco. !Ni se te ocurra utilizar este middleware en producción!

Los test de integración

La configuración del TestServer es sencilla. Lo único que tengo que hacer es crear la clase Startup que va a definir el comportamiento del servidor y allí, configurar el middleware de autenticación personalizada justo antes de usar mi api. Por ejemplo, así:

    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseTestServerAuthentication();

            var config = new HttpConfiguration();
            Sample.Api.WebApiConfiguration.Configure(config);
            app.UseWebApi(config);
        }
    }

En los ejemplos, voy usar xUnit. Cada test que se ejecuta, crea una nueva instancia de la clase que lo contiene. Además utiliza mecanismos estándar (nada de atributos) para la inicialización de los tests (en el constructor de la clase) y para la limpieza (implementar la interfaz IDisposable).

Por lo tanto, en el contexto de mis test, crearé la instancia del TestServer en el constructor de la clase de test de la siguiente forma:

        private readonly TestServer _server;
        private readonly HttpClient _userHttpCient;

        public VauesWithDefaultUserTests()
        {
            _server = TestServer.Create<Startup>();
            _userHttpCient = _server.HttpClient
                .WithDefaultIdentity(Identities.User);
        }

La magia ocurre en ese método de extensión WithDefaultIdentity . En este caso, añade una cabecera por defecto a todas las peticiones que se hagan con el HttpClient que configura. En esta cabecera, viajará la información necesaria para que el middleware de autenticación que hemos configurado cree la instancia del ClaimsPrincipal que representará la petición.

De la misma forma, hay otro método de extensión sobre la clase RequestBuilder que nos permitirá configurar las credenciales de una única petición:

        [Fact]
        public async Task WithRequestBuilder()
        {
            var response = await _server.CreateRequest("api/values")
                .WithIdentity(Identities.User)
                .GetAsync();

            response.EnsureSuccessStatusCode();
        }

Los dos métodos de extensión aceptan el mismo tipo de parámetro: un IEnumerable<Claim> en el que definiremos la información que tenga nuestro usuario en el contexto de las peticiones al api. Por ejemplo, en los ejemplos de antes, he creado una clase estática que definirá todas las identidades que quiera utilizar en el contexto de los tests:

    public static class Identities
    {
        public static readonly IEnumerable User = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, "1"),
            new Claim(ClaimTypes.Name, "User"),
        };
    }

Con estos dos métodos de extensión y con el middleware configurado en la clase Startup que utiliza el servidor de test, es todo lo que necesitamos para poder realizar peticiones autenticadas a cualquier api.

Sencillo, ¿no? Desde luego, ya no tienes excusa para no tener unos bonitos test de integración de tu WebApi.

¡Que lo disfrutes!

Si tenéis curiosidad, el código fuente está aquí: https://github.com/hbiarge/Acheve.Owin.Testing.Security. En la carpeta samples podrás encontrar un ejemplo completo de uso de la librería.