Glosario · Fundamentos de testing
Test double (doble de prueba)
En inglés: Test double
También llamado: test doubles, doble de prueba, dobles de prueba, mock
Definición
Cualquier objeto que sustituye a una dependencia real en un test, como un dummy, stub, fake, spy o mock, para que el código bajo prueba se ejecute rápido y predecible.
Un test double (doble de prueba) sustituye una dependencia real, como una pasarela de pago, una base de datos, un reloj o un servicio de correo, por algo que el test controla. El nombre, popularizado por Gerard Meszaros, viene de los dobles de acción del cine. Los dobles hacen que los tests sean rápidos y deterministas, y permiten simular situaciones difíciles de provocar de verdad, como un timeout o una tarjeta rechazada.
Los cinco tipos
| Tipo | Qué hace | Ejemplo |
|---|---|---|
| Dummy | Se pasa solo para rellenar un parámetro y nunca se usa | Un null o un logger vacío que el método exige pero que este test nunca toca |
| Stub | Devuelve respuestas predefinidas a las llamadas | rates.getRate("EUR") siempre devuelve 0.92 |
| Spy | Un stub que además registra cómo se le llamó | Registra que send() se llamó una vez con una dirección dada |
| Mock | Viene preprogramado con expectativas, y el test verifica las interacciones | Verificar que gateway.charge(4999) se llamó exactamente una vez |
| Fake | Una implementación funcional pero simplificada | Un repositorio en memoria respaldado por un HashMap |
En el día a día se usa “mock” para todos ellos, y librerías como Mockito crean objetos que pueden actuar como stubs, spies o mocks. Aun así, conocer la diferencia ayuda, sobre todo stub frente a mock: un stub proporciona entradas al código bajo prueba (verificación de estado), mientras que un mock comprueba las salidas que el código envía a sus dependencias (verificación de comportamiento).
Ejemplo (JUnit 5 con Mockito)
@Test
void declinedCardDoesNotSendConfirmation() {
PaymentGateway gateway = mock(PaymentGateway.class);
EmailService email = mock(EmailService.class);
when(gateway.charge(4999)).thenReturn(PaymentResult.DECLINED); // stub
var checkout = new Checkout(gateway, email);
var result = checkout.placeOrder(4999);
assertEquals(OrderStatus.PAYMENT_FAILED, result.status());
verify(email, never()).sendConfirmation(any()); // mock-style verification
}
(Se omiten los imports estáticos de org.mockito.Mockito, org.mockito.ArgumentMatchers y org.junit.jupiter.api.Assertions.)
Errores comunes
- Hacer mock de todo. Los tests que verifican cada llamada interna se rompen con cada refactorización y pueden pasar mientras la integración real está rota. Usa mocks en las fronteras de la arquitectura; dentro de ellas, prefiere objetos reales o fakes.
- Dobles que se alejan de la realidad. Una API simulada con un stub que devuelve un campo que el servicio real renombró hace meses da una falsa confianza. Los tests de contrato y unos cuantos tests de integración reales mantienen honestos a los dobles.
- Hacer mock de tipos que no son tuyos. Envuelve los clientes de terceros en tu propia interfaz y crea el doble de esa interfaz.