Pruebas en C#: Unit, Integration, E2E de la A a la Z

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: ¿Por qué son necesarias las pruebas en C#?

\n

En el desarrollo moderno con C# (.NET), la calidad del código no es un lujo, sino una necesidad. Las pruebas no solo permiten detectar errores antes de llegar a producción, sino que también sirven como documentación viva y aceleran la refactorización. Existen tres niveles principales de pruebas que forman la "pirámide de pruebas": Unit (unitarias), Integration (de integración) y E2E (de extremo a extremo). En este artículo analizaremos cada uno de ellos con ejemplos de código real en C#.

\n\n

1. Pruebas unitarias: El fundamento de la calidad

\n

Las pruebas unitarias verifican un fragmento de código aislado (método, clase) sin dependencias externas (bases de datos, sistema de archivos, API). En el ecosistema .NET, tres frameworks son populares: xUnit (recomendado para proyectos nuevos), NUnit y MSTest.

\n\n

Principios fundamentales (FIRST)

\n
    \n
  • Fast — rápidas (milisegundos).
  • \n
  • Isolated — aisladas (usamos mocks).
  • \n
  • Repeatable — reproducibles.
  • \n
  • Self-validating — con autoverificación (Assert).
  • \n
  • Timely — oportunas.
  • \n
\n\n

Ejemplo de prueba unitaria con xUnit y Moq

\n

Supongamos que tenemos un servicio para calcular descuentos:

\n
public interface IDiscountService\n{\n    decimal CalculateDiscount(decimal price, bool isLoyaltyMember);\n}\n\npublic class DiscountService : IDiscountService\n{\n    public decimal CalculateDiscount(decimal price, bool isLoyaltyMember)\n    {\n        if (isLoyaltyMember) return price * 0.9m; // 10% de descuento\n        return price;\n    }\n}
\n

Prueba usando Moq (para simular dependencias) y xUnit:

\n
using Xunit;\nusing Moq;\n\npublic class DiscountServiceTests\n{\n    [Fact]\n    public void CalculateDiscount_WhenLoyaltyMember_ReturnsDiscountedPrice()\n    {\n        // Arrange\n        var service = new DiscountService();\n        var price = 100m;\n\n        // Act\n        var result = service.CalculateDiscount(price, true);\n\n        // Assert\n        Assert.Equal(90m, result);\n    }\n\n    [Theory]\n    [InlineData(100, false, 100)]\n    [InlineData(200, true, 180)]\n    public void CalculateDiscount_MultipleCases_ReturnsCorrectValues(decimal price, bool isLoyalty, decimal expected)\n    {\n        var service = new DiscountService();\n        var result = service.CalculateDiscount(price, isLoyalty);\n        Assert.Equal(expected, result);\n    }\n}
\n

Consejo: Use el atributo [Fact] para pruebas individuales y [Theory] + [InlineData] para pruebas parametrizadas.

\n\n

2. Pruebas de integración: Verificación de la conexión de componentes

\n

Las pruebas de integración verifican cómo varios módulos funcionan juntos — por ejemplo, un controlador, un servicio y una base de datos real (o su sustituto, como SQLite en memoria). En ASP.NET Core se utiliza WebApplicationFactory para esto.

\n\n

Configuración de WebApplicationFactory

\n

Creemos una clase de prueba que levante un host de prueba web:

\n
using Microsoft.AspNetCore.Mvc.Testing;\nusing Microsoft.EntityFrameworkCore;\nusing Microsoft.Extensions.DependencyInjection;\nusing MyApp.Data;\n\npublic class CustomWebApplicationFactory : WebApplicationFactory<Program>\n{\n    protected override void ConfigureWebHost(IWebHostBuilder builder)\n    {\n        builder.ConfigureServices(services =>\n        {\n            // Eliminamos el DbContext real\n            var descriptor = services.SingleOrDefault(\n                d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));\n            if (descriptor != null) services.Remove(descriptor);\n\n            // Agregamos base de datos en memoria\n            services.AddDbContext<AppDbContext>(options =>\n                

Blogs

Book Recommendations