Los servicios son esas partes de tu código donde vive la lógica de negocio. Si los controladores son la fachada de tu aplicación, los servicios son el "cerebro". Los errores en los servicios pueden llevar a la ejecución incorrecta de los procesos de negocio.
Probar los servicios ayuda a:
- Comprobar que la lógica de la aplicación funciona como debería.
- Asegurarse de que todas las llamadas a métodos e interacciones con dependencias se realizan correctamente.
- Detectar bugs antes de compilar y desplegar la aplicación.
Mockito como tu mejor amigo
Para probar los servicios vamos a usar activamente Mockito. ¿Por qué? Porque necesitamos testear la lógica de negocio aislándola de todas las dependencias externas, como bases de datos o APIs externas.
Pasos principales para testear servicios
- Crear mocks para todas las dependencias del servicio (repositorios, otros servicios, etc.).
- Configurar el comportamiento de los mocks usando
when()ythenReturn(). - Comprobar que los métodos del servicio provocan las acciones esperadas y devuelven resultados correctos.
- Opcionalmente, verificar que las dependencias se llamen el número de veces esperado (o que no se llamen), usando métodos como
verify().
Práctica: pruebas de servicios
Tomemos un ejemplo. Tenemos un servicio que calcula descuentos para usuarios. Accede al repositorio para obtener información del usuario y otra lógica de la aplicación.
Servicio
@Service
public class DiscountService {
private final UserRepository userRepository;
public DiscountService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public double calculateDiscount(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("User not found"));
if (user.isPremium()) {
return 20.0; // 20% de descuento para usuarios premium
}
return 5.0; // 5% de descuento para el resto
}
}
Pruebas
1. Creamos la clase de test y configuramos los mocks:
@ExtendWith(MockitoExtension.class)
class DiscountServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private DiscountService discountService;
// Datos de prueba
private final User premiumUser = new User(1L, "PremiumUser", true);
private final User regularUser = new User(2L, "RegularUser", false);
// ...
}
2. Probamos el método calculateDiscount() para usuarios premium:
@Test
void shouldReturn20PercentDiscountForPremiumUsers() {
// Configuramos el mock
when(userRepository.findById(1L)).thenReturn(Optional.of(premiumUser));
// Acción
double discount = discountService.calculateDiscount(1L);
// Comprobación
assertEquals(20.0, discount);
verify(userRepository, times(1)).findById(1L); // Aseguramos que el repositorio se llamó exactamente 1 vez
}
3. Probamos el método calculateDiscount() para usuarios normales:
@Test
void shouldReturn5PercentDiscountForRegularUsers() {
// Configuramos el mock
when(userRepository.findById(2L)).thenReturn(Optional.of(regularUser));
// Acción
double discount = discountService.calculateDiscount(2L);
// Comprobación
assertEquals(5.0, discount);
verify(userRepository, times(1)).findById(2L);
}
4. Manejamos el caso cuando el usuario no se encuentra:
@Test
void shouldThrowExceptionWhenUserNotFound() {
// Configuramos el mock
when(userRepository.findById(anyLong())).thenReturn(Optional.empty());
// Comprobación de la excepción
Exception exception = assertThrows(IllegalArgumentException.class,
() -> discountService.calculateDiscount(99L));
assertEquals("User not found", exception.getMessage());
verify(userRepository, times(1)).findById(99L);
}
Ahora estamos seguros de que nuestro servicio funciona correctamente y responde ante todos los escenarios posibles.
Introducción a las pruebas de repositorios
Los repositorios son el puente entre tu aplicación y la base de datos. Por eso es importante asegurarse de que interactúan correctamente con la base, ejecutando consultas y procesamientos adecuados.
¿Cómo testear repositorios?
- Usar una base de datos embebida, por ejemplo, H2 (en memoria) para los tests.
- La anotación
@DataJpaTestayuda a configurar automáticamente el entorno de pruebas para trabajar con repositorios JPA. - Verificar que los resultados de las consultas coinciden con lo esperado.
Práctica: Pruebas de repositorios
Entidad
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private boolean isPremium;
// Constructores, getters y setters
}
Repositorio
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findAllByIsPremium(boolean isPremium);
}
Pruebas
1. Creamos la clase de test:
@DataJpaTest
class UserRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
// ...
}
2. Probamos el método findAllByIsPremium:
@Test
void shouldFindAllPremiumUsers() {
// Preparación de datos de prueba
User premiumUser = new User(null, "PremiumUser", true);
User regularUser = new User(null, "RegularUser", false);
entityManager.persist(premiumUser);
entityManager.persist(regularUser);
entityManager.flush();
// Acción
List<User> result = userRepository.findAllByIsPremium(true);
// Comprobación
assertEquals(1, result.size());
assertEquals("PremiumUser", result.get(0).getName());
}
3. Probamos los métodos estándar de JPA (por ejemplo, findById):
@Test
void shouldFindUserById() {
// Preparación de datos
User user = new User(null, "TestUser", false);
User savedUser = entityManager.persistFlushFind(user);
// Comprobación
Optional<User> result = userRepository.findById(savedUser.getId());
assertTrue(result.isPresent());
assertEquals("TestUser", result.get().getName());
}
Conclusiones
Probar los servicios con Mockito aísla la lógica de las dependencias externas, permitiéndote enfocarte sólo en verificar la lógica de negocio. Mientras que testear los repositorios con la anotación @DataJpaTest y la base H2 ayuda a garantizar la corrección de las consultas y la manipulación de datos.
Has preparado con éxito tu base de código para tareas reales, y los bugs ya no podrán esconderse en tu aplicación. Ahora puedes avanzar a escenarios de testing más complejos, que verás en las siguientes lecciones.
GO TO FULL VERSION