Cuando pruebas una aplicación, a menudo pasa que tu código depende de otros componentes, como servicios, repositorios, APIs externas o incluso librerías de terceros. En vez de ejecutar el código real de esas dependencias en los tests (y gastar un montón de recursos), podemos usar mocks — "trucos" que imitan el comportamiento de los objetos reales. Y aquí entra en escena Mockito.
Imagina esto: estás escribiendo un test para un servicio que consulta la base de datos. Una llamada real a la BD podría tardar demasiado, y además podría crear una dependencia fuerte de SQL... esas cosas pasan. En lugar de eso, usando Mockito, simplemente sustituimos la llamada a la base por un objeto especial que devolverá un resultado preparado de antemano. ¿Cómodo? ¡Claro que sí!
Conceptos básicos de Mockito
Antes de pasar a la práctica, aclaremos qué hay detrás de los términos mocks, stubs y spies.
- Mock (Mock) — es un objeto que imita el comportamiento de un objeto real. Gracias a él podemos definir respuestas predefinidas para llamadas concretas.
- Stub (Stub) — parecido a un mock, pero menos flexible. Es simplemente un objeto "limpio" con comportamiento definido de antemano, que se usa para las pruebas.
- Spy (Spy) — es un objeto real, pero con la opción de espiar cómo se usa. A veces necesitas la funcionalidad real del objeto, pero poder sustituir partes de su comportamiento.
Empezando con Mockito
Para usar Mockito, añadamos su dependencia al proyecto.
Conectar Mockito vía Maven
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.0.0</version> <!-- Sustituye por la versión actual -->
<scope>test</scope>
</dependency>
Si usas Gradle:
testImplementation 'org.mockito:mockito-core:5.0.0' // Sustituye por la versión actual
También merece la pena añadir JUnit si aún no está en el proyecto.
Principales funcionalidades de Mockito
1. Crear mocks
Puedes crear un mock con el método mock():
import static org.mockito.Mockito.*;
SomeService someServiceMock = mock(SomeService.class);
Sin embargo, si usas JUnit 5, recomendamos usar la anotación @Mock para mayor legibilidad. Para ello necesitarás la anotación @ExtendWith(MockitoExtension.class).
Ejemplo:
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class SomeServiceTest {
@Mock
private SomeService someServiceMock;
// Tus tests aquí
}
2. Configurar el comportamiento de los mocks
Después de crear el mock, lo interesante es definir su comportamiento. Por ejemplo, queremos que un método devuelva un valor específico:
when(someServiceMock.someMethod()).thenReturn("Mocked Value");
Ejemplo de uso:
@Test
void testMockBehavior() {
when(someServiceMock.greet("John")).thenReturn("Hello, mocked John!");
String result = someServiceMock.greet("John");
assertEquals("Hello, mocked John!", result);
}
when().thenReturn() te permite literalmente "enseñar" al mock qué debe hacer.
3. Verificación de llamadas
Mockito no solo permite definir comportamiento, sino también verificar si se llamó un método y cuántas veces.
@Test
void testMethodInvocation() {
someServiceMock.greet("John");
verify(someServiceMock).greet("John"); // Comprobación: ¿se llamó el método greet con el parámetro "John"?
verify(someServiceMock, times(1)).greet("John"); // Comprobación: se llamó exactamente una vez
}
Anotaciones de Mockito
Para simplificar el código, Mockito ofrece varias anotaciones útiles:
- @Mock: Crea un mock para el objeto indicado.
@Mock private SomeService someServiceMock; - @InjectMocks: Inyecta automáticamente los mocks en las dependencias del objeto que se está probando.
@InjectMocks private SomeController someController;Ejemplo:
@Test void testController() { when(someServiceMock.greet("John")).thenReturn("Hello, John!"); String response = someController.greet("John"); assertEquals("Hello, John!", response); } - @Spy: Crea un objeto real, pero permite sustituir algunos de sus métodos.
@Spy private SomeService realService;
Práctica: probar un servicio
Tenemos un servicio GreetingService que depende de TimeProvider para formar el saludo, teniendo en cuenta la hora actual:
public class GreetingService {
private final TimeProvider timeProvider;
public GreetingService(TimeProvider timeProvider) {
this.timeProvider = timeProvider;
}
public String getGreeting(String name) {
int currentHour = timeProvider.getCurrentHour();
if (currentHour < 12) {
return "Good morning, " + name + "!";
} else {
return "Good afternoon, " + name + "!";
}
}
}
En los tests mockeamos TimeProvider para comprobar el comportamiento del servicio:
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class GreetingServiceTest {
private final TimeProvider timeProviderMock = mock(TimeProvider.class);
private final GreetingService greetingService = new GreetingService(timeProviderMock);
@Test
void testMorningGreeting() {
when(timeProviderMock.getCurrentHour()).thenReturn(9); // Mañana
String greeting = greetingService.getGreeting("John");
assertEquals("Good morning, John!", greeting);
}
@Test
void testAfternoonGreeting() {
when(timeProviderMock.getCurrentHour()).thenReturn(15); // Tarde
String greeting = greetingService.getGreeting("John");
assertEquals("Good afternoon, John!", greeting);
}
}
TimeProvider se puede considerar una dependencia externa que hemos mockeado.
Errores comunes al trabajar con Mockito
Algunos fallos pueden arruinar tus tests inesperadamente:
- NullPointerException: Te olvidas de inicializar los mocks. Solución: usa
@Mocky@ExtendWith(MockitoExtension.class). - Desajuste entre comportamiento y llamada: Si el método se llama con parámetros distintos a los que especificaste en
when(). Revisa siempre los argumentos. - Comportamiento excesivo en los mocks: "Enseña" a los mocks solo lo que necesitas para ese test concreto; si no, el test será difícil de mantener.
Mockito es una herramienta potente que hace que probar sistemas complejos sea más fácil y cómodo. Ahora sabes cómo crear mocks, configurar su comportamiento y verificar interacciones. En las siguientes lecciones verás cómo aplicar todo esto para probar controllers y otros componentes de aplicaciones Spring.
GO TO FULL VERSION