Tabla de Contenidos

07_2 - Capa persistencia: Test

Para los test de la capa de persistencia usaremos las siguientes dependencias:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>${org.spring.boot.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>${org.spring.boot.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-csv</artifactId>
    <version>${org.apache.commons.csv.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>${com.h2database.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.flywaydb</groupId>
    <artifactId>flyway-core</artifactId>
    <version>${org.flywaydb.version}</version>
    <scope>test</scope>
</dependency>

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<Long> expectedAuthorIds = newBook.getAuthors().stream()
                .map(AuthorJpaEntity::getId)
                .collect(Collectors.toSet());
        Set<Long> 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)
        );
    }