Cada clase de pruebas requiere anotaciones específicas para integrarse con el contexto de Spring y JPA.
@DataJpaTest
@Transactional
@Import(CompanyService.class)
public class CompanyServiceTest
{
@Autowired
private CompanyService companyService;
@Autowired
private TestEntityManager entityManager;
private PodamFactory factory =
new PodamFactoryImpl();
private List<CompanyEntity> companyList =
new ArrayList<>();
}¿Qué hace cada anotación?
Es una herramienta proporcionada por Spring Test que permite interactuar con la base de datos de pruebas de forma manual. Se usa para:
Diferencia clave: El CompanyService que probamos usa CompanyRepository (interfaz de persistencia). El TestEntityManager es nuestro acceso directo a la BD para preparar datos o validar resultados sin pasar por el servicio.
El setUp es un método especial que se ejecuta automáticamente antes de cada test. Su propósito es preparar un estado inicial conocido y consistente para que cada test comience bajo las mismas condiciones.
¿Por qué es necesario? Sin setUp, los tests podrían interferir entre sí:
Solución: El setUp limpia la BD y carga datos frescos antes de cada test, garantizando aislamiento total.
JUnit ejecuta cada test siguiendo este ciclo:
El método setUp típicamente hace dos cosas: limpiar la BD y luego insertar datos de prueba.
@BeforeEach
void setUp()
{
// 1. Limpiar todos los datos existentes
clearData();
// 2. Insertar datos frescos para las pruebas
insertData();
}
// ──────────────────────────────────────
// Método auxiliar: Limpiar la base de datos
// ──────────────────────────────────────
private void clearData()
{
entityManager.getEntityManager()
.createQuery("delete from CompanyEntity")
.executeUpdate();
}
// ──────────────────────────────────────
// Método auxiliar: Insertar datos de prueba
// ──────────────────────────────────────
private void insertData()
{
for (int i = 0; i < 3; i++)
{
CompanyEntity entity =
factory.manufacturePojo(CompanyEntity.class);
entityManager.persist(entity);
companyList.add(entity);
}
}Desglose paso a paso:
📌 clearData() — Limpieza de la BD
📌 insertData() — Carga de datos de prueba
Buena pregunta. Técnicamente, cada test se revierte automáticamente por @Transactional (la BD vuelve al estado previo después de cada test). Sin embargo, clearData() sigue siendo importante porque:
En resumen: No es estrictamente necesario técnicamente, pero es una buena práctica de limpieza explícita que hace los tests más robustos y fáciles de entender.
Insertar múltiples registros (típicamente 3-5) es útil porque:
Regla práctica: Inserta entre 3 y 5 elementos. No demasiados (lentitud) ni muy pocos (tests poco realistas).
La variable companyList se declara como variable de instancia en la clase de test:
public class CompanyServiceTest
{
@Autowired
private CompanyService companyService;
@Autowired
private TestEntityManager entityManager;
private PodamFactory factory =
new PodamFactoryImpl();
// Lista que almacena las compañías insertadas en setUp()
private List<CompanyEntity> companyList =
new ArrayList<>();
}Esta lista se limpia automáticamente en cada setUp() porque se crea como new ArrayList<>(), pero puedes también hacer companyList.clear() al inicio de insertData() para ser más explícito.
Podam (POjo DAta Maker) es una librería Java que genera automáticamente instancias de clases con datos aleatorios válidos. Es especialmente útil en pruebas unitarias porque elimina la necesidad de crear manualmente objetos de prueba con todos sus atributos.
¿Cómo funciona? Podam usa reflexión para inspeccionar la clase, identificar sus atributos (String, Long, Date, etc.) y generar valores aleatorios apropiados para cada tipo.
Supongamos que tienes esta entidad:
@Entity
public class BookEntity
{
@Id
@GeneratedValue
private Long id;
private String name;
private String isbn;
private Date publishDate;
private Integer pages;
// getters y setters...
}En lugar de crear manualmente el objeto así:
BookEntity book = new BookEntity();
book.setName("El Quijote");
book.setIsbn("978-1234567890");
book.setPublishDate(new Date());
book.setPages(863);
// Repetir esto para cada test...Con Podam, lo haces en una línea:
PodamFactory factory = new PodamFactoryImpl();
BookEntity book = factory.manufacturePojo(BookEntity.class);
// book ya tiene todos los campos inicializados con valores aleatorios
// name = "algún texto aleatorio"
// isbn = "otro texto aleatorio"
// publishDate = alguna fecha aleatoria
// pages = algún número aleatorioCuando tienes entidades con relaciones bidireccionales (donde A conoce a B y B conoce a A), Podam puede entrar en un loop infinito al intentar generar los objetos.
Ejemplo del problema:
@Entity
public class BookEntity
{
@Id
private Long id;
private String name;
// Relación: un libro tiene un autor
@ManyToOne
private AuthorEntity author;
}@Entity
public class AuthorEntity
{
@Id
private Long id;
private String name;
// Relación inversa: un autor tiene muchos libros
@OneToMany(mappedBy = "author")
private List<BookEntity> books;
}La anotación @PodamExclude le indica a Podam que ignore ese atributo y no intente generar datos para él. Se coloca sobre el atributo que queremos excluir (típicamente el lado inverso de la relación).
import uk.co.jemos.podam.common.PodamExclude;
@Entity
public class AuthorEntity
{
@Id
private Long id;
private String name;
private Date birthDate;
// ✅ Excluir esta colección para evitar loop infinito
@PodamExclude
@OneToMany(mappedBy = "author")
private List<BookEntity> books;
// getters y setters...
}Ahora al generar un BookEntity:
PodamFactory factory = new PodamFactoryImpl();
BookEntity book = factory.manufacturePojo(BookEntity.class);
// ✅ book tiene:
// - id, name, isbn, publishDate → valores aleatorios
// - author → un AuthorEntity con id, name, birthDate aleatorios
// - author.books → null (porque está excluido con @PodamExclude)
// ✅ No hay loop infinitoLos escenarios de prueba son casos de uso específicos que verifican que tu código se comporta correctamente. Cada escenario es un método anotado con @Test que ejecuta el servicio bajo condiciones específicas y valida el resultado.
Hay dos tipos principales de escenarios:
Por cada método del servicio necesitas al menos un test positivo y un test negativo por cada regla de negocio. Si createCompany() valida 2 reglas, necesitas 1 positivo + 2 negativos = 3 tests mínimo.
Este test verifica que el método createCompany() funciona correctamente cuando se proporciona una compañía válida (sin violaciones de reglas).
@Test
void testCreateCompany()
{
// Paso 1: Generar datos de prueba
CompanyEntity newEntity =
factory.manufacturePojo(CompanyEntity.class);
// Paso 2: Invocar el método bajo prueba
CompanyEntity result =
companyService.createCompany(newEntity);
// Paso 3: Verificar que retornó un resultado
assertNotNull(result);
// Paso 4: Buscar en BD usando TestEntityManager
CompanyEntity entity = entityManager
.find(CompanyEntity.class, result.getId());
// Paso 5: Verificar que se persistió correctamente
assertEquals(newEntity.getName(), entity.getName());
assertEquals(newEntity.getDescription(),
entity.getDescription());
}Desglose paso a paso:
Este test verifica que el método createCompany() rechaza correctamente una compañía cuando se viola la regla de negocio "no puede haber dos compañías con el mismo nombre".
@Test
void testCreateInvalidCompany()
{
// assertThrows: verifica que el código lanza una excepción
assertThrows(IllegalOperationException.class, () ->
{
// Paso 1: Generar una compañía nueva
CompanyEntity newEntity =
factory.manufacturePojo(CompanyEntity.class);
// Paso 2: Cambiar el nombre para que sea igual a una existente
// companyList tiene datos cargados en setUp()
newEntity.setName(companyList.get(0).getName());
// Paso 3: Intentar crear (debería fallar)
companyService.createCompany(newEntity);
});
}Desglose paso a paso:
Para cada método del servicio, sigue esta estrategia:
1. Identifica qué pruebas necesitas:
2. Ejemplo con createBook():
3. Nombres descriptivos:
| Método del Servicio | Reglas de Negocio | Tests Positivos | Tests Negativos | Total Tests |
|---|---|---|---|---|
| createCompany() | 1 | 1 | 1 | 2 |
| createBook() | 2 | 1 | 2 | 3 |
| createDepartment() | 1 | 1 | 1 | 2 |