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:
- Arrancar tu API en segundo plano (en memoria, sin abrir navegador ni puertos reales).
- Darte un
HttpClientpre-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.TestingHacer 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 { }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
}
}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");
});
});
}
}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 ...
}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);
});
}