====== 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) ); }