Pruebas de Integración en C# con Docker: Cómo Eliminar los Problemas de Entorno
Todo desarrollador de C# ha enfrentado esta situación: las pruebas pasan en la máquina local pero fallan en el servidor de CI. La razón suele ser diferencias en el entorno: versión de base de datos, configuración de Redis, servicios de prueba faltantes. Las pruebas de integración en C# con Docker resuelven este problema de una vez por todas. En este artículo, veremos cómo configurar un entorno de prueba aislado usando contenedores, escribir pruebas de integración reales e integrarlas en un pipeline de CI/CD.
Por qué Docker es la Herramienta Perfecta para Pruebas de Integración en C#
Las pruebas de integración verifican la interacción entre componentes del sistema: aplicación + base de datos, API + caché, microservicios entre sí. Sin Docker, tendrías que:
- Instalar PostgreSQL, SQL Server, Redis en cada máquina de desarrollo
- Configurar el servidor de CI con las mismas versiones
- Limpiar datos entre ejecuciones de pruebas
Docker resuelve todas estas tareas. Describes la infraestructura en código (Dockerfile, docker-compose.yml) y se despliega de manera idéntica en cualquier sistema. Para C# (.NET) esto es especialmente relevante ya que Microsoft apoya activamente la contenerización: imágenes oficiales de .NET SDK, SQL Server en un contenedor Linux, integración con Azure DevOps.
Configuración del Proyecto: Testcontainers + xUnit
Para las pruebas de integración en C# con Docker, usaremos la librería Testcontainers (disponible en NuGet). Gestiona automáticamente el ciclo de vida del contenedor: se inicia antes de las pruebas, se detiene al finalizar.
Paso 1. Instalación de Paquetes
Crea un nuevo proyecto para pruebas o abre uno existente. Agrega los paquetes NuGet:
dotnet add package Testcontainersdotnet add package Testcontainers.PostgreSqldotnet add package xunitdotnet add package FluentAssertionsPaso 2. Clase Base para Pruebas con Contenedores
Creemos una clase abstracta que inicie PostgreSQL antes de ejecutar las pruebas:
using Testcontainers.PostgreSql;using Xunit;
public abstract class IntegrationTestBase : IAsyncLifetime{ private readonly PostgreSqlContainer _postgresContainer;
protected string ConnectionString => _postgresContainer.GetConnectionString();
protected IntegrationTestBase() { _postgresContainer = new PostgreSqlBuilder() .WithImage("postgres:15-alpine") .WithDatabase("testdb") .WithUsername("test") .WithPassword("test123") .Build(); }
public async Task InitializeAsync() { await _postgresContainer.StartAsync(); }
public async Task DisposeAsync() { await _postgresContainer.StopAsync(); }}La clase implementa IAsyncLifetime de xUnit — el contenedor se iniciará antes de la primera prueba y se detendrá después de la última. El método GetConnectionString() devuelve el puerto dinámico que Docker asignó al contenedor.
Paso 3. Escribiendo una Prueba de Integración
Ahora escribamos una prueba que verifique el trabajo del repositorio con una base de datos real:
public class UserRepositoryTests : IntegrationTestBase{ [Fact] public async Task CreateUser_Should_SaveToDatabase() { // Arrange await using var connection = new NpgsqlConnection(ConnectionString); await connection.OpenAsync(); var repository = new UserRepository(connection); var user = new User { Name = "Ivan", Email = "ivan@example.com" };
// Act var userId = await repository.CreateAsync(user);
// Assert userId.Should().BeGreaterThan(0); var savedUser = await repository.GetByIdAsync(userId); savedUser.Should().NotBeNull(); savedUser.Name.Sho