Que herramientas usamos para hacer test de integración en DotNet

A la hora de empezar con los test de integración tenemos varias formas de realizarlos, en este artículo os enseñare las utilidades que utilizamos para hacer testing de integración desde Visual Studio y con DotNet Core.

¿Qué entiendo por Test de Integración?

Según unas de las definiciones que nos encontramos por Internet los test de integración son aquellas que se realizan en el ámbito del desarrollo de software una vez que se han aprobado las pruebas unitarias y lo que prueban es que todos los elementos unitarios que componen el software funcionan juntos correctamente probándolos en grupo.

Partiendo de esta definición vamos a un escenario “común”. Este escenario es tenemos una API Rest y queremos testear todos los endpoint de los que tenemos, por ejemplo, tenemos una API que nos devuelve todos nuestros Avengers favoritos tal y como se visualiza aqui:

Solución Visual Studio

Antes de empezar con los herramientas vamos a descifrar que elementos son los que tiene esta solución y así podremos ver como lo testearemos y que herramienta usaremos. Recapitulando:

  1. Sistema de autenticación contra el Azure Active Directory
  2. Base de Datos SQL Azure usando Entity Framework

¿Que debemos de testear y como lo debemos de testear?

Para la parte del sistema de autenticación, al final lo que debemos de cubrir en un test es alguien que no esté autenticado cuando intente llamar a nuestra API le dé un código 401 Unauthorized y cuando este autenticado pues el token tenga la información necesaria para que nuestra aplicación funcione. Por lo tanto tendremos que tener algo que nos simule dicho comportamiento para ello en primer lugar .NET Core nos trae una herramienta Test Host en el que desde un proyecto de Test podemos levantar nuestra aplicación e indicarle en el StartUp la configuración que vamos a lanzar desde el proyecto de Test. Aunque como veremos después en el código es una librería muy potente y que trae muchísimas utilidades, para el tema de la seguridad deja la libertad al usuario de como obtener los tokens JWT, pues bien hay una libreria OpenSource Acheve implementada por Hugo Biarge que nos facilita la vida.

Ahora bien, para la segunda parte, el tema de la base de datos siempre es algo que hay que tener en cuenta, si va a ser un test de integración deberemos de ejecutarlo en una base de datos y no podremos mockearla. Ahora bien, estos Test en qué momento se van a ejecutar o quien los van a ejecutar. Dependiendo de estas contestaciones tenemos varias alternativas que pueden ser muy validas

Show me the Code

Una vez ya realizadas las aclaraciones sobre cómo vamos a librar algunos inconvenientes a la hora de realizar los Test, centrémonos en lo importante en el Test y como implementarlo.

XUnit

La herramienta para realizarlos es xUnit, atrás han quedado los tiempos de MS-Test y .NET Core nos proporciona una librería de Test que si se utiliza correctamente es bastante potente.

En primer lugar, vamos a crearnos nuestro servidor de Test, para ello tendremos que crear un Startup que sera el punto de arranque. Lo que solomos hacer es crearnos un arranque que herede el StartUp que tenemos en la API y en dicho arranque lo que vamos a realizar es sobreescribir los métodos que vamos a realizar en los test.

public class TestStartup : Startup
   {
       public TestStartup(IConfiguration configuration, IWebHostEnvironment environment) : base(configuration, environment)
       {
       }
   }

Tal y como tenemos montado el StartUp tenemos un método donde Configuramos la Autenticación y ahora para el test lo que vamos a realizar es indicarle que coja un esquema que está dentro del Test Server (en lugar del esquema que tenemos puesto en la API )

        protected override void ConfigureAuthentication(IServiceCollection services)
       {
           services.AddMvc().AddApplicationPart(typeof(AvengerController).Assembly);
           services.AddAuthentication(options =>
           {
               options.DefaultScheme = TestServerDefaults.AuthenticationScheme;
           })
           .AddTestServer();
       }

El código expuesto no tiene mucho misterio solo la primera línea que esto lo que hará es que cuando realicemos una petición usando el TestServer podamos invocar directamente a la clase del controlador en lugar de a la url que se levanta.

Una vez tenemos resuelto el tema de la autenticación, vamos con la parte de la base de datos. En este caso vamos a utilizar un SQLLite que se genera en memoria, para ello pondremos el siguiente código

   protected override void ConfigureBD(IServiceCollection services)
       {
           var connectionStringBuilder = new SqliteConnectionStringBuilder { DataSource = ":memory:" };
           var connectionString = connectionStringBuilder.ToString();
           var connection = new SqliteConnection(connectionString);
           services
             .AddEntityFrameworkSqlite()
             .AddDbContext<AppDbContext>(
               options => options.UseSqlite(connection)
             );
       }

Vale la base de datos ya la tenemos in Memory pero mucha veces de cara a los test es interesante que tengamos datos ya cargados de pruebas. Para ello lo que vamos a hacer uso de Bogus una librería que nos permite multitud de opciones para crearnos unos datos de test o de pruebas muy útiles. Para ello en el mismo método añadimos la siguiente funcionalidad para insertar los datos (se podría usar el Seed propio de EF pero si es algo que no lo vamos a tener en el código de producción a mí personalmente me gusta más separarlo por dejarlo más limpito)

var serviceProvider = services.BuildServiceProvider();
var storeContext = serviceProvider.GetRequiredService<AppDbContext>();
   storeContext.Database.OpenConnection(); 
   storeContext.Database.EnsureCreated();
   storeContext.Database.Migrate();
   if (!storeContext.Avenger.Any())
   {
       var avengerList = new Faker<Avenger>().Generate(10);
       storeContext.AddRange(avengerList);                              
       storeContext.SaveChanges();
       }

Una de las cosas muy chulas que tiene xUnit es la posibilidad de inyectar a tus pruebas un estado previo en el que se encuentra, esto podemos decir que es el TestFixture. Nos crearemos un TestFixture en el que se creara el Test de Pruebas y también aquellos aspectos que serán necesarios para los test. En este caso yo me he creado una Claim que será la que se utilice para la autenticación.

   public class TestFixture : IDisposable
   {
       public TestServer TestServer { get; private set; }
       public HttpClient Client { get; private set; }
       public string TenantIdClaimType => "http://schemas.microsoft.com/identity/claims/tenantid";        

       public List<Claim> DefaultIdentity
       {
           get
           {
               return new List<Claim>()
               {
                   new Claim(ClaimTypes.Name, "Hulk"),
                   new Claim(TenantIdClaimType, "3ab8g5de-32c5-3134-zfdarf-182a67a344"),
                   new Claim(ClaimTypes.Upn, "hulk@theavenger.com")
               };
           }
       }
       public TestFixture()
       {
           CreateServer();
       }
       private void CreateServer()
       {
           var path = Assembly.GetAssembly(typeof(TestFixture))
               .Location;

           Log.Logger = new LoggerConfiguration()
               .MinimumLevel.Verbose()
               .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
               .Enrich.FromLogContext()
               .WriteTo.Console()
               .CreateLogger();

           var hostBuilder = new WebHostBuilder()
               .UseContentRoot(Path.GetDirectoryName(path))
               .ConfigureServices(services=>
               {
                   services.AddAutofac();
               })                
               .UseSerilog()
               .ConfigureAppConfiguration(cb =>
               {
                   cb.AddJsonFile("appsettings.json", optional: false)
                   .AddEnvironmentVariables();
               })
               .UseStartup<TestStartup>();            
           this.TestServer = new TestServer(hostBuilder);
           this.Client = TestServer.CreateClient();
       }

       public void Dispose()
       {
           if (Client != null)
           {
               Client.Dispose();
           }
           if (TestServer != null)
           {
               TestServer.Dispose();
           }
       }
   }

Una vez ya tenemos creado el TestServer y el TestFixture vamos a lo que realmente importa los Test. En primer lugar y por convención cada uno de los controladores de nuestra API que vamos a testear los llamamos escenarios. Nos creamos una clase en la que le inyectemos la Fixture para ello tendremos algo similar al siguiente código.

    [Trait("Category", "Integration")]
    public class AvengerScenarios : IClassFixture<TestFixture>
    {
        private readonly TestFixture fixture;

        public AvengerScenarios(TestFixture fixture)
        {
            this.fixture = fixture;            
        }

    }

Luego implementaremos el Test, por ejemplo, vamos a testear que el método que nos devuelve a todos los Avengers. Para ello nos tenemos que asegurar que esta autenticado, para ello usaremos el método WithIdentity que nos proporciona Acheve. Luego usaremos la librería Fluent Assertions el principal motivo es por legibilidad de los test que los hace mucho más similares al lenguaje humano.

      [Fact]
        public async Task Get_AllAvengers_returnOk()
        {
            var response = await fixture.TestServer
                .CreateHttpApiRequest<AvengerController>(controller => controller.Get())
                .WithIdentity(fixture.DefaultIdentity)
                .GetAsync();
            response.StatusCode.Should().Be(HttpStatusCode.OK);
        }

Resumen

En este articulo hemos visto el Tooling de herramienta que utilizo para los Test de Integración.

Acheve.TestHost Bogus FluenAsserttions Microsoft.AspNetCore.TestHost Microsoft.EntityFramewokcore.SQlite Microsoft.EntityFramewokcore.InMemory xUnit

Este tipo de Test, también se pueden hacer con herramientas como Postman sin embargo yo creo que desde Visual Studio y tal y como esta planteada esta forma nos aporta un suficiente valor por ejemplo de que cuando se realice algún cambio en algún método del controlador el Test directamente No compila, en cambio sí lo lanzamos desde un Postman la primera vez que lo lancemos nos devolverá un BadRequest y es un tiempo de cambio y de rehacer el test que no es tan fácil.

Junto con estas herramientas hay alguna otra herramienta como Moq.Net, Coverlet que también utilizamos para otro tipo de Test o para obtener el porcentaje de cobertura de nuestros Test (aunque como muchas veces digo el porcentaje no es algo que me importe, sino más bien que tengamos testeadas las partes core de la aplicación).

¿Y vosotros cual utilizáis? ¿Cuál echas de menos?

Happy Codding