Pruebas en C#: de Unit a E2E — cómo construir una arquitectura de calidad para verificaciones en .NET
La calidad del software depende directamente del sistema de pruebas. En el ecosistema de C# y .NET existe una pirámide de pruebas bien establecida: pruebas unitarias (unit), de integración (integration) y de extremo a extremo (end-to-end). En este artículo analizaremos cada nivel con ejemplos prácticos, mostraremos qué bibliotecas usar y proporcionaremos plantillas de código listas para usar.
1. Pruebas unitarias: la base de la estabilidad del código
Las pruebas unitarias verifican el funcionamiento de métodos, clases o componentes individuales de forma aislada de las dependencias externas (BD, API, sistema de archivos). El objetivo principal es encontrar rápidamente regresiones durante la refactorización.
Herramientas para C#
- NUnit — framework clásico con atributos
[Test],[TestCase]. - xUnit — framework moderno de los creadores de NUnit, preferido en proyectos nuevos.
- Moq — biblioteca para crear objetos mock.
- FluentAssertions — para aserciones legibles.
Ejemplo de prueba unitaria con xUnit y Moq
Supongamos que tenemos un servicio de cálculo de descuento que consulta un repositorio de usuarios:
public interface IUserRepository{ bool IsPremiumUser(int userId);}
public class DiscountService{ private readonly IUserRepository _userRepo; public DiscountService(IUserRepository userRepo) => _userRepo = userRepo;
public decimal CalculateDiscount(int userId, decimal amount) { if (_userRepo.IsPremiumUser(userId)) return amount * 0.9m; // 10% de descuento return amount; }}
// Pruebapublic class DiscountServiceTests{ [Fact] public void CalculateDiscount_ForPremiumUser_Returns90Percent() { // Arrange var mockRepo = new Mock<IUserRepository>(); mockRepo.Setup(r => r.IsPremiumUser(It.IsAny<int>())).Returns(true); var service = new DiscountService(mockRepo.Object);
// Act var result = service.CalculateDiscount(1, 100m);
// Assert Assert.Equal(90m, result); }}Consejo: Use [Theory] y [InlineData] en xUnit para pruebas parametrizadas — esto reduce la duplicación de código.
2. Pruebas de integración: verificación de la conexión entre componentes
Las pruebas de integración verifican cómo sus módulos interactúan con sistemas externos reales: bases de datos, colas, servicios web. A diferencia de las pruebas unitarias, aquí no se usan mocks — levantamos entornos reales.
Bibliotecas y enfoques
- TestContainers — ejecuta contenedores Docker (PostgreSQL, SQL Server, Redis) directamente desde las pruebas.
- WebApplicationFactory (de Microsoft.AspNetCore.Mvc.Testing) — crea un host de prueba para ASP.NET Core.
- Respawn — limpia la BD entre pruebas sin recrear el contenedor.
Ejemplo de prueba de integración con TestContainers y WebApplicationFactory
Verificaremos que nuestra API guarda correctamente un usuario en PostgreSQL:
public class UserApiIntegrationTests : IClassFixture<TestContainersFixture>{ private readonly HttpClient _client; private readonly TestContainersFixture _fixture;
public UserApiIntegrationTests(TestContainersFixture fixture) { _fixture = fixture; var factory = new WebApplicationFactory<Program>() .WithWebHostBuilder(builder => { builder.ConfigureTestServices(services => { // Reemplazamos la cadena de conexión real por la del contenedor services.RemoveAll<DbContextOptions<AppDbContext>>(); se