====== 07_2 - Capa persistencia: Test ======
Para los test de la capa de persistencia usaremos las siguientes dependencias:
* [[https://www.h2database.com/html/main.html|H2]]: Base de datos embebida en memoria\\ \\
* [[https://www.red-gate.com/products/flyway/|Flywaydb]]: Herramienta de migración de base de datos\\ \\
* [[https://spring.io/projects/spring-data-jpa|Spring Data Jpa]]: Implementación de Jakarta Persistence de Spring
org.springframework.boot
spring-boot-starter-data-jpa
${org.spring.boot.version}
test
org.springframework.boot
spring-boot-starter-test
${org.spring.boot.version}
test
org.apache.commons
commons-csv
${org.apache.commons.csv.version}
test
com.h2database
h2
${com.h2database.version}
test
org.flywaydb
flyway-core
${org.flywaydb.version}
test
Los tests de los mapeadores y repositorios los haremos como los de la capa de dominio, usando Mockito cuando sea necesario (para mockear los DAO).
===== Test DAO =====
Para testear los DAO, necesitamos levantar una base de datos temporal en memoria. H2 es ideal para esto porque se ejecuta embebida, sin necesidad de un servidor externo, y se reinicia desde cero en cada ejecución de test.
==== Configurando H2 ====
En el fichero **src/test/resources/application-test.properties**, añadimos la configuración de la base de datos en memoria:
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MYSQL
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
Para poblar la bbdd de pruebas, usaremos Flyway.
==== Migraciones con Flyway ====
Cuando se ejecutan los tests de persistencia, necesitamos que la base de datos (H2 en memoria) esté correctamente inicializada con las tablas y datos que usarán los DAO o repositorios. Ahí es donde entra Flyway: ejecuta automáticamente los scripts de migración SQL antes de que empiecen los tests.
Por convención, Flyway busca los scripts SQL dentro de **src/main/resources/db/migration**. Cada archivo debe tener un nombre con el siguiente formato:
V1__init.sql
V2__insert_data.sql
V3__add_constraints.sql
...
El prefijo **V** indica una versión (migración incremental). El doble guion bajo __ separa el número de versión del nombre descriptivo.
Si tus migraciones están en otra carpeta, puedes configurarlo en application.properties:
spring.flyway.locations=classpath:/migraciones
**@DataJpaTest** levanta un contexto mínimo de Spring con JPA, H2 y transacciones configuradas automáticamente. Al terminar cada test, la transacción se revierte, dejando la BD limpia.
@DataJpaTest
class PublisherDaoJpaImplTest {
Además, tendremos que crear los Bean de Spring para que funcione la inyección de dependencias. Para eso, creamos una clase de configuración:
@Configuration
@EnableJpaRepositories(basePackages = "es.cesguiro.persistence.dao.jpa")
@EntityScan(basePackages = "es.cesguiro.persistence.dao.jpa.entity")
public class TestConfig {
@Bean
public PublisherJpaDao publisherJpaDao(EntityManager entityManager) {
return new PublisherJpaDaoImpl();
}
@Bean
public BookJpaDao bookJpaDao(EntityManager entityManager) {
return new BookJpaDaoImpl();
}
@Bean
public AuthorJpaDao authorJpaDao(EntityManager entityManager) {
return new AuthorJpaDaoImpl();
}
}
De esta forma, cuando tenga que inyectar, por ejemplo, BookJpaDao, Spring buscará en su contenedor de Beans si existe y será capaz de hacer la inyección de forma automática.
@DataJpaTest
@ContextConfiguration(classes = TestConfig.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class BookJpaDaoImplTest {
@PersistenceContext
private EntityManager entityManager;
@Autowired
private BookJpaDao bookJpaDao;
@Autowired
private PublisherJpaDao publisherJpaDao;
@Autowired
private AuthorJpaDao authorJpaDao;
@Test
@DisplayName("Test insert method persists BookJpaEntity")
void testInsert() {
PublisherJpaEntity publisherJpaEntity = new PublisherJpaEntity();
publisherJpaEntity.setName("Editorial X");
publisherJpaEntity.setSlug("editorial-x");
entityManager.persist(publisherJpaEntity);
AuthorJpaEntity authorJpaEntity1 = new AuthorJpaEntity();
authorJpaEntity1.setName("Author One");
entityManager.persist(authorJpaEntity1);
AuthorJpaEntity authorJpaEntity2 = new AuthorJpaEntity();
authorJpaEntity2.setName("Author Two");
entityManager.persist(authorJpaEntity2);
BookJpaEntity newBook = new BookJpaEntity(
null,
"666666666666",
"New Book Title ES",
"New Book Title EN",
"New Book Synopsis ES",
"New Book Synopsis EN",
BigDecimal.valueOf(29.99),
10.0,
"new_book_cover.jpg",
LocalDate.of(2024, 1, 1).toString(),
publisherJpaEntity, // Assuming the first publisher exists
List.of(authorJpaEntity1, authorJpaEntity2) // Assuming the first two authors exist
);
String sql = "SELECT COUNT(b) FROM BookJpaEntity b";
long countBefore = entityManager.createQuery(sql, Long.class)
.getSingleResult();
BookJpaEntity result = bookJpaDao.insert(newBook);
long countAfter = entityManager.createQuery(sql, Long.class)
.getSingleResult();
long lastId = entityManager.createQuery("SELECT MAX(b.id) FROM BookJpaEntity b", Long.class)
.getSingleResult();
Set expectedAuthorIds = newBook.getAuthors().stream()
.map(AuthorJpaEntity::getId)
.collect(Collectors.toSet());
Set resultAuthorIds = result.getAuthors().stream()
.map(AuthorJpaEntity::getId)
.collect(Collectors.toSet());
assertAll(
() -> assertNotNull(result),
() -> assertEquals(lastId, result.getId()),
() -> assertEquals(newBook.getIsbn(), result.getIsbn()),
() -> assertEquals(newBook.getTitleEs(), result.getTitleEs()),
() -> assertEquals(newBook.getTitleEn(), result.getTitleEn()),
() -> assertEquals(newBook.getSynopsisEs(), result.getSynopsisEs()),
() -> assertEquals(newBook.getSynopsisEn(), result.getSynopsisEn()),
() -> assertEquals(newBook.getBasePrice(), result.getBasePrice()),
() -> assertEquals(newBook.getDiscountPercentage(), result.getDiscountPercentage()),
() -> assertEquals(newBook.getCover(), result.getCover()),
() -> assertEquals(newBook.getPublicationDate(), result.getPublicationDate()),
() -> assertEquals(newBook.getPublisher().getId(), result.getPublisher().getId()),
() -> assertEquals(newBook.getAuthors().size(), result.getAuthors().size()),
() -> assertEquals(expectedAuthorIds, resultAuthorIds),
() -> assertEquals(countBefore + 1, countAfter)
);
}