Tipos de Dobles: Stub, Mock, Fake y Spy
Un stub devuelve respuestas preparadas. Un mock además registra cómo lo llamaron para poder verificarlo. Un fake es una implementación real pero simplificada (un repositorio en memoria). Un spy envuelve un objeto real y permite sobrescribir solo algunos métodos.
En una línea Un TareaRepositoryEnMemoria implements TareaRepository con un HashMap es un fake; mock(TareaRepository.class) es un mock.
public interface TareaRepository {
Optional<Tarea> findById(long id);
Tarea save(Tarea tarea);
}
// Fake: implementación real pero en memoria, reutilizable en todos los tests
public class TareaRepositoryEnMemoria implements TareaRepository {
private final Map<Long, Tarea> datos = new HashMap<>();
@Override public Optional<Tarea> findById(long id) { return Optional.ofNullable(datos.get(id)); }
@Override public Tarea save(Tarea tarea) { datos.put(tarea.id(), tarea); return tarea; }
}
// Con el fake compruebas ESTADO
var repo = new TareaRepositoryEnMemoria();
repo.save(new Tarea(1L, "Escribir tests", false));
new TareaService(repo).completar(1L);
assertThat(repo.findById(1L)).get().extracting(Tarea::hecha).isEqualTo(true);
// Con el mock compruebas CONVERSACIÓN
TareaRepository mock = mock(TareaRepository.class);
when(mock.findById(1L)).thenReturn(Optional.of(new Tarea(1L, "Escribir tests", false)));
new TareaService(mock).completar(1L);
verify(mock).save(argThat(Tarea::hecha));
Cómo funciona por dentro
La terminología viene de Gerard Meszaros: «doble de prueba» es cualquier objeto que sustituye a uno real en un test. Un dummy solo rellena un parámetro. Un stub devuelve respuestas fijas. Un spy es un stub que además registra cómo lo llamaron. Un mock viene con expectativas sobre las llamadas que debe recibir. Un fake tiene una implementación que funciona, pero con un atajo: un repositorio en memoria, un servidor de correo falso.
Mockito difumina las diferencias: mock() crea un objeto que sirve como stub (when) y como mock (verify), y spy() envuelve un objeto real. Lo importante es saber qué estás comprobando: el estado resultante (con stubs o fakes) o la conversación entre objetos (con mocks).
Los fakes merecen más uso del que suelen tener. Un TareaRepositoryEnMemoria se escribe una vez, se reutiliza en todos los tests del servicio y permite comprobar el estado final (repo.findById(1)) en lugar de las llamadas internas, lo que hace los tests mucho más resistentes a los refactors.
spy sobre la propia clase que pruebas para cambiar parte de su comportamiento: es señal de que la clase hace demasiadas cosas.
Se conecta con Programar contra interfacesRepositorio en memoriaTests frágiles