====== 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.