aspnet-core-integration-testing-webapplicationfactory

Pruebas de integración en ASP.NET Core

  • 4 min

Una prueba de integración comprueba que varias piezas de la aplicación funcionan juntas.

Los tests unitarios son fantásticos para verificar la lógica de negocio (2 + 2 = 4). Pero una API web es mucho más que lógica aislada.

Una API tiene:

  • Un sistema de Routing que debe encontrar la URL correcta.
  • Un Model Binder que debe parsear el JSON.
  • Una Validación que debe rechazar datos incorrectos.
  • Una Base de Datos que debe guardar y leer información real.

¿De qué sirve que tu lógica sea perfecta si el controlador falla al serializar la respuesta o si la consulta SQL está mal escrita?

En este punto aparecen los Integration Tests (Pruebas de Integración).

En lugar de probar una clase aislada, vamos a levantar la API completa en memoria, lanzar una petición HTTP real y ver qué responde.

La herramienta clave: WebApplicationFactory

Microsoft nos ofrece el paquete Microsoft.AspNetCore.Mvc.Testing.

Esta librería contiene la clase WebApplicationFactory<T>, que es capaz de:

  1. Arrancar tu API en segundo plano (en memoria, sin abrir navegador ni puertos reales).
  2. Darte un HttpClient pre-configurado para llamar a esa API en memoria.

Configuración del proyecto

En tu proyecto de tests (MiApi.Tests), instala el paquete:

dotnet add package Microsoft.AspNetCore.Mvc.Testing
Copied!

Hacer visible Program.cs

Desde .NET 6, el Program generado por top-level statements queda como internal. Para que el proyecto de tests pueda verlo y arrancar la API, añade una clase parcial al final de tu Program.cs de la API, no del test:

var app = builder.Build();
// ... configuración ...
app.Run();

public partial class Program { }
Copied!

Escribir la primera prueba de integración

Vamos a probar un endpoint GET /api/productos. Queremos asegurar que devuelve un código 200 y una lista de productos.

using Microsoft.AspNetCore.Mvc.Testing;
using Xunit;

// Implementamos IClassFixture para que la API se levante una sola vez
// y se reutilice entre tests (por rendimiento).
public class ProductosIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public ProductosIntegrationTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Fact]
    public async Task GetProductos_DevuelveSuccessYJson()
    {
        // 1. Arrange: Creamos un cliente HTTP que "vive" en el test
        var client = _factory.CreateClient();

        // 2. Act: Llamamos al endpoint real
        var response = await client.GetAsync("/api/productos");

        // 3. Assert
        response.EnsureSuccessStatusCode(); // Verifica que sea 2xx

        var respuestaString = await response.Content.ReadAsStringAsync();
        Assert.Contains("Teclado", respuestaString); // Verificamos contenido
    }
}
Copied!

Acabas de probar el routing, el controlador, la inyección de dependencias y la respuesta JSON, todo a la vez.

La base de datos de pruebas

El test anterior tiene un peligro: está usando la base de datos real configurada en tu appsettings.json. Si ejecutas la prueba, podrías modificar datos de desarrollo o producción.

Para los tests de integración, debemos reemplazar la base de datos real por una base de datos desechable.

Reemplazar servicios con ConfigureWebHost

Podemos heredar de WebApplicationFactory y sobrescribir la configuración para cambiar el DbContext real por uno de test. Para ejemplos simples puedes usar InMemory de EF Core; si quieres comprobar SQL real, mejor SQLite en memoria o Testcontainers.

using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

public class MiApiFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            // Sustituimos el DbContext de SQL Server
            services.RemoveAll<DbContextOptions<ApplicationDbContext>>();
            services.RemoveAll<ApplicationDbContext>();

            // Añadimos una base de datos en memoria para las pruebas
            services.AddDbContext<ApplicationDbContext>(options =>
            {
                options.UseInMemoryDatabase("InMemoryDbForTesting");
            });
        });
    }
}
Copied!

Ahora, en nuestros tests, usamos MiApiFactory en lugar de la genérica.

public class ProductosTests : IClassFixture<MiApiFactory> // 👈 Usamos la nuestra
{
    // ... el test es idéntico, pero ahora es seguro ...
}
Copied!

Sustituir servicios externos

A veces no solo queremos cambiar la BBDD. Imagina que tu API envía emails reales al registrarse. No quieres enviar 100 emails cada vez que pases los tests.

Podemos usar la misma técnica para reemplazar el IEmailService real por un Mock.

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
    builder.ConfigureServices(services =>
    {
        // ... cambiar BBDD ...

        // Reemplazar IEmailService por un Mock
        services.RemoveAll<IEmailService>();

        var mockEmail = new Mock<IEmailService>();
        services.AddSingleton(mockEmail.Object);
    });
}
Copied!