1. ¿Qué es y para qué sirve?

Es la práctica de probar la unidad más pequeña de código posible (normalmente un Método) de forma aislada.

  • Aislada: Significa que NO conectamos con la Base de Datos, ni con APIs externas, ni con el sistema de archivos. Todo eso se simula (Mock).

  • Objetivo: Verificar que tu lógica funciona, sin depender de que el servidor SQL esté encendido.

  • Beneficio: Te da una red de seguridad. Si rompes algo al refactorizar, el test te avisa en 0.5 segundos.


2. El “Stack” de Herramientas (La Trinidad)

En .NET moderno, usamos estas tres librerías juntas:

  1. xUnit: El motor que ejecuta los tests.

    • Nos da el atributo [Fact] para marcar un método como prueba.
  2. NSubstitute: La herramienta de engaño (Mocking).

    • Nos permite crear objetos falsos (Substitute.For<IInterface>) para simular servicios.
  3. FluentAssertions: La herramienta de validación legible.

    • Nos permite escribir resultado.Should().Be(5) en lugar de Assert.Equal(5, resultado).

3. El Patrón Sagrado: AAA

Todo test debe seguir esta estructura visual. Si mezclas las fases, el test es ilegible.

  • Arrange (Preparar): Creas los Mocks, preparas los datos de prueba (UserDto) e instancias la clase que vas a probar (Controller).

  • Act (Actuar): Ejecutas la línea de código real que quieres probar.

    • Ejemplo: var result = await controller.CreateUser(dto);
  • Assert (Verificar): Compruebas que el resultado es el esperado y que se llamó a las dependencias correctas.


4. Conceptos Clave: Mocks y Stubs

Como no usamos base de datos real, necesitamos “actores” que finjan ser ella.

  • Configurar el Mock (Stubbing): Enseñarle al actor qué decir.

    C#

    // "Cuando te pidan usuarios, devuelve esta lista vacía"
    mockService.GetAllUsersAsync().Returns(new List<UserDto>());
    
  • Verificar el Mock (Spying): Comprobar si el actor fue llamado.

    C#

    // "¿Te llamaron exactamente 1 vez con estos datos?"
    await mockService.Received(1).CreateUserAsync(Arg.Any<UserDto>());
    

5. Estrategia de Testing (¿Qué pruebo?)

No basta con probar que funciona. Debes probar:

  1. Happy Path (Camino Feliz): Todo va bien. El servicio devuelve 200/201.

  2. Sad Path (Validación): El usuario envía basura. El controlador devuelve 400.

  3. Edge Cases (Errores): El servicio falla o lanza excepción. El controlador devuelve 500 (o lo maneja el Global Handler).


📝 Tu “Snippet” Maestro (Plantilla)

Copia esto en tus notas. Es la estructura perfecta de un test moderno en .NET 10.

C#

using FluentAssertions;
using Microsoft.AspNetCore.Mvc;
using NSubstitute;
using Xunit;

public class UsersControllerTests
{
    [Fact] // 1. Etiqueta obligatoria
    public async Task CreateUser_ShouldReturn201_WhenDataIsValid()
    {
        // --- ARRANGE (Preparar) ---
        // Crear el falso servicio
        var mockService = Substitute.For<IUserService>();
        
        // Crear el controlador real inyectándole el falso
        var controller = new UsersController(mockService);
        
        // Datos de prueba
        var userDto = new UserDto(1, "Test", "test@test.com");

        // --- ACT (Ejecutar) ---
        var result = await controller.CreateUser(userDto);

        // --- ASSERT (Verificar) ---
        // 1. Verificar el tipo de respuesta HTTP
        var objectResult = result.Should().BeOfType<ObjectResult>().Subject;
        objectResult.StatusCode.Should().Be(201);

        // 2. Verificar que el controlador llamó al servicio (Nivel Pro)
        // Usamos Arg.Is para inspeccionar los datos que viajaron
        await mockService.Received(1).CreateUserAsync(
            Arg.Is<UserDto>(u => u.Email == "test@test.com")
        );
    }
}

⚠️ Error Común de Junior

Confundir Unit Test con Integration Test.

  • Si tu test necesita que arranques Docker o que haya una base de datos real instalada… NO es unitario.

  • Un Unit Test corre en memoria RAM y tarda milisegundos.