====== 05 - Pruebas unitarias: JUnit y Mockito ======
Una **prueba unitaria** comprueba una unidad de código — normalmente un método o una clase — aislada de sus dependencias. **JUnit 5** es el framework estándar en Java para escribirlas y **Mockito** permite simular las dependencias.
===== 1. Estructura de una prueba =====
Las pruebas siguen el patrón **AAA** (*Arrange-Act-Assert*): preparar, actuar, comprobar.
@Test
void calculatesTotalWithDiscount() {
// Arrange
PriceCalculator calculator = new PriceCalculator();
// Act
Money total = calculator.totalWithDiscount(100, 20);
// Assert
assertEquals(80, total.amount());
}
^ Anotación ^ Función ^
| ''@Test'' | Marca un método como prueba |
| ''@DisplayName'' | Descripción legible de la prueba |
| ''@BeforeEach'' | Se ejecuta antes de cada prueba |
| ''@AfterEach'' | Se ejecuta después de cada prueba |
| ''@Disabled'' | Desactiva temporalmente la prueba |
Con Maven, ''mvn test'' compila y ejecuta todas las pruebas.
===== 2. Aserciones =====
Las aserciones comparan el resultado esperado con el real. Si fallan, la prueba falla y muestra la diferencia.
assertEquals(expected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(value);
assertThrows(IllegalArgumentException.class, () -> calculator.totalWithDiscount(-1, 0));
===== 3. Ciclo de vida =====
class CalculatorTest {
private PriceCalculator calculator;
@BeforeEach
void setUp() {
calculator = new PriceCalculator();
}
@Test
void calculatesTotal() { /* ... */ }
}
''@BeforeEach'' crea un estado limpio para cada prueba: ninguna depende de la anterior.
===== 4. Mockito: dobles de prueba =====
Una unidad aislada no puede tocar la base de datos ni la red. Un **doble de prueba** sustituye una dependencia por una simulación controlada. Con **Mockito** se declara el mock, se configura su comportamiento y se comprueba su uso.
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock
OrderRepository repository;
@InjectMocks
OrderService service;
@Test
void returnsOrderWhenFound() {
Order stored = new Order(42L);
when(repository.findById(42L)).thenReturn(Optional.of(stored));
Order result = service.findById(42L);
assertEquals(stored, result);
verify(repository).findById(42L);
}
}
^ Herramienta ^ Función ^
| ''@Mock'' | Crea el doble |
| ''when(...).thenReturn(...)'', ''given(...)'' | Define qué devuelve el doble |
| ''@InjectMocks'' | Inyecta los dobles en el objeto probado |
| ''verify(...)'' | Comprueba que se llamó al doble como se esperaba |
===== 5. Integración entre capas =====
En un monolito por capas, las pruebas unitarias también cubren la **colaboración entre capas**, simulando la capa inferior. Por ejemplo, probar el ''controller'' con el ''service'' mockeado comprueba que el controlador delega y traduce correctamente, sin ejecutar la lógica real:
@ExtendWith(MockitoExtension.class)
class BookControllerTest {
@Mock
BookService service;
@Test
void mapsServiceResultToResponse() {
when(service.findById(1L)).thenReturn(new Book(1L, "Clean Code"));
BookResponse response = new BookController(service).get(1L);
assertEquals("Clean Code", response.title());
}
}
Así se comprueba el **contrato entre capas**: qué se pide y qué se devuelve, no la implementación de cada capa.
Las unitarias no tocan la base de datos ni la red. Si una prueba necesita infraestructura real, ya no es unitaria: se prueba en el nivel de integración o E2E según corresponda.