En esta lección nos centraremos en el aspecto práctico: escribir unit-tests para servicios. Crearemos tests para los componentes clave de la lógica de negocio de uno de los microservicios, usando herramientas que ya conoces.
Preparación para escribir Unit-tests
Los unit-tests son la base del testing durante el desarrollo. Comprueban pequeñas porciones de código, aisladas de dependencias externas. Por ejemplo, quieres probar un método de servicio que calcula un descuento, sin tocar la base de datos, el API de otro servicio o librerías externas.
Objetivos:
- Aislar el código bajo prueba.
- Comprobar la corrección de la lógica de negocio.
- Asegurarse de que condiciones límite y situaciones excepcionales se manejan correctamente.
Entorno para las pruebas
Para empezar, asegúrate de que tu proyecto está configurado para testing. Comprueba que tienes las siguientes dependencias:
pom.xml para Maven:
<dependencies>
<!-- JUnit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<!-- Mockito -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
build.gradle para Gradle:
dependencies {
// JUnit 5
testImplementation 'org.junit.jupiter:junit-jupiter'
// Mockito
testImplementation 'org.mockito:mockito-core'
// Spring Boot Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Ejemplo de aplicación: tienda de descuentos
Tomemos como ejemplo un microservicio para procesar descuentos. Tenemos un servicio DiscountService que calcula el importe del descuento en función de la categoría del usuario y la cantidad de artículos comprados.
Aquí está el servicio:
import org.springframework.stereotype.Service;
@Service
public class DiscountService {
public double calculateDiscount(String userCategory, int itemCount) {
if (itemCount <= 0) {
throw new IllegalArgumentException("Item count must be greater than 0");
}
switch (userCategory.toLowerCase()) {
case "vip":
return itemCount * 0.2; // descuento del 20%
case "regular":
return itemCount > 5 ? itemCount * 0.1 : 0.0; // descuento del 10% si compras > 5
default:
return 0.0; // Sin descuento
}
}
}
Paso 1: Configurar la clase de test
Siguiendo las convenciones, creemos la clase de test DiscountServiceTest. Asegúrate de que está en el directorio src/test/java.
package com.example.service;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* Pruebas para DiscountService.
*/
public class DiscountServiceTest {
private final DiscountService discountService = new DiscountService();
@Test
void testCalculateDiscountForVipUser() {
double discount = discountService.calculateDiscount("VIP", 10);
assertEquals(2.0, discount, "VIP user should get 20% discount");
}
@Test
void testCalculateDiscountForRegularUser() {
double discount = discountService.calculateDiscount("regular", 6);
assertEquals(0.6, discount, "Regular user should get 10% discount for more than 5 items");
}
@Test
void testCalculateDiscountForUnknownUser() {
double discount = discountService.calculateDiscount("guest", 5);
assertEquals(0.0, discount, "Guest users should not get a discount");
}
@Test
void testCalculateDiscountWithZeroItems() {
assertThrows(IllegalArgumentException.class, () ->
discountService.calculateDiscount("VIP", 0),
"Should throw exception when item count is zero or less"
);
}
}
Discusión de los tests
- Escenarios positivos: los tests comprueban el resultado correcto para usuarios "VIP" y "regular".
- Escenarios negativos: test con la categoría "guest" y test con cantidad de artículos cero.
- Excepciones: comprobar que para parámetros no válidos se lanza la excepción
IllegalArgumentException.
Paso 2: Añadir interacción con Mock (Mockito)
Ahora supongamos que DiscountService depende de otro servicio, por ejemplo UserRepository, que devuelve información del usuario. Podemos usar Mockito para crear un mock.
Inyectemos la dependencia:
@Service
public class DiscountService {
private final UserRepository userRepository;
public DiscountService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public double calculateDiscount(String userId, int itemCount) {
if (itemCount <= 0) {
throw new IllegalArgumentException("Item count must be greater than 0");
}
String userCategory = userRepository.getUserCategory(userId);
switch (userCategory.toLowerCase()) {
case "vip":
return itemCount * 0.2;
case "regular":
return itemCount > 5 ? itemCount * 0.1 : 0.0;
default:
return 0.0;
}
}
}
Probamos con Mockito:
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
public class DiscountServiceTest {
private UserRepository userRepository;
private DiscountService discountService;
@BeforeEach
void setUp() {
userRepository = mock(UserRepository.class);
discountService = new DiscountService(userRepository);
}
@Test
void testCalculateDiscountWithMockedUser() {
when(userRepository.getUserCategory("123")).thenReturn("VIP");
double discount = discountService.calculateDiscount("123", 10);
assertEquals(2.0, discount);
verify(userRepository, times(1)).getUserCategory("123");
}
@Test
void testCalculateDiscountForUnknownUser() {
when(userRepository.getUserCategory("456")).thenReturn("guest");
double discount = discountService.calculateDiscount("456", 5);
assertEquals(0.0, discount);
verify(userRepository, times(1)).getUserCategory(anyString());
}
}
- Creación del mock: creamos un mock para
UserRepository— una implementación falsa que no está ligada a la base de datos real. - Stub de métodos: usamos
when().thenReturn()para indicar el valor devuelto. - Verificación de llamadas:
verify()comprueba que el método fue llamado en el mock con los parámetros indicados.
Errores comunes y cómo solucionarlos
- Mocking incorrecto: si olvidas indicar
when().thenReturn(), el mock devolveránullpor defecto. - No usar
verify: verificar llamadas ayuda a asegurarse de que el método probado interactúa con el mock correctamente. - Excepciones no manejadas: si no compruebas las excepciones, los tests pueden pasar por alto errores críticos.
Aplicación práctica
Las habilidades para escribir unit-tests son útiles para cualquier desarrollo backend. En entrevistas suelen pedir saber mockear dependencias y escribir tests para lógica de negocio compleja. En proyectos reales esto acelera el desarrollo, porque los errores se detectan en etapas tempranas.
GO TO FULL VERSION