Aller au contenu principal

Chapitre 4: Tests d'Intégration

Introduction​

Les tests unitaires avec mocks sont rapides mais ne garantissent pas que les composants fonctionnent ensemble. Les tests d'intégration vérifient que Spring, JPA, et votre base de données collaborent correctement.


🎯 Objectifs du Chapitre​

À la fin de ce chapitre, vous saurez:

  1. ✅ Utiliser @SpringBootTest
  2. ✅ Tester les repositories avec @DataJpaTest
  3. ✅ Tester les controllers avec @WebMvcTest
  4. ✅ Configurer les profils de test
  5. ✅ Utiliser TestContainers (bonus)

1. @SpringBootTest​

Test d'Intégration Complet​

@SpringBootTest  // ← Charge le contexte Spring complet
class ApplicationIntegrationTest {

@Autowired
private UserService userService;

@Autowired
private UserRepository userRepository;

@Test
void contextLoads() {
// Vérifie que l'application démarre
assertNotNull(userService);
}

@Test
void shouldCreateAndFindUser() {
User user = userService.create("John", "john@test.com");

User found = userService.findById(user.getId());

assertEquals("John", found.getName());
}
}

Options de @SpringBootTest​

// Contexte complet avec serveur web
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class FullStackTest {
@LocalServerPort
private int port;
}

// Sans serveur web (plus rapide)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
class ServiceTest { }

// Avec configuration spécifique
@SpringBootTest(properties = {
"spring.datasource.url=jdbc:h2:mem:testdb",
"logging.level.root=WARN"
})
class ConfiguredTest { }

2. @DataJpaTest​

Test de Repository​

@DataJpaTest  // ← Configure uniquement JPA + H2
class UserRepositoryTest {

@Autowired
private UserRepository userRepository;

@Autowired
private TestEntityManager entityManager;

@Test
void shouldFindByEmail() {
// Arrange
User user = new User("John", "john@test.com");
entityManager.persistAndFlush(user);

// Act
Optional<User> found = userRepository.findByEmail("john@test.com");

// Assert
assertTrue(found.isPresent());
assertEquals("John", found.get().getName());
}

@Test
void shouldReturnEmptyWhenEmailNotFound() {
Optional<User> found = userRepository.findByEmail("unknown@test.com");

assertFalse(found.isPresent());
}
}

Ce que @DataJpaTest Configure​

@DataJpaTest inclut:
├── H2 en mémoire (par défaut)
├── Hibernate/JPA
├── TestEntityManager
├── Repositories
└── @Transactional (rollback après chaque test)

N'inclut PAS:
├── @Service
├── @Controller
├── Spring Security
└── Autres composants

Tester les Requêtes Personnalisées​

@DataJpaTest
class ProductRepositoryTest {

@Autowired
private ProductRepository productRepository;

@Autowired
private TestEntityManager em;

@BeforeEach
void setUp() {
em.persist(new Product("Laptop", 1000.0, "Electronics"));
em.persist(new Product("Phone", 800.0, "Electronics"));
em.persist(new Product("Shirt", 50.0, "Clothing"));
em.flush();
}

@Test
void shouldFindByCategory() {
List<Product> electronics = productRepository.findByCategory("Electronics");

assertThat(electronics).hasSize(2);
}

@Test
void shouldFindByPriceRange() {
List<Product> products = productRepository.findByPriceBetween(100.0, 900.0);

assertThat(products)
.hasSize(1)
.extracting(Product::getName)
.containsExactly("Phone");
}

@Test
void shouldCountByCategory() {
long count = productRepository.countByCategory("Electronics");

assertEquals(2, count);
}
}

3. @WebMvcTest​

Test de Controller (sans service réel)​

@WebMvcTest(UserController.class)  // ← Teste uniquement ce controller
class UserControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean // ← Mock le service
private UserService userService;

@Test
void shouldReturnUser() throws Exception {
// Arrange
when(userService.findById(1L))
.thenReturn(new User(1L, "John", "john@test.com"));

// Act & Assert
mockMvc.perform(get("/api/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John"))
.andExpect(jsonPath("$.email").value("john@test.com"));
}

@Test
void shouldReturn404WhenUserNotFound() throws Exception {
when(userService.findById(anyLong()))
.thenThrow(new UserNotFoundException(999L));

mockMvc.perform(get("/api/users/999"))
.andExpect(status().isNotFound());
}
}

@MockBean vs @Mock​

// @MockBean: Pour les tests Spring (injecté dans le contexte)
@WebMvcTest
class ControllerTest {
@MockBean
private UserService userService; // Remplace le bean Spring
}

// @Mock: Pour les tests unitaires purs (Mockito seul)
@ExtendWith(MockitoExtension.class)
class ServiceTest {
@Mock
private UserRepository userRepository; // Mock Mockito simple
}

4. Profils de Test​

application-test.properties​

# src/test/resources/application-test.properties

# Base de données H2
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create-drop

# Désactiver des features
spring.flyway.enabled=false
spring.mail.host=localhost

Activer le Profil​

@SpringBootTest
@ActiveProfiles("test") // ← Utilise application-test.properties
class MyIntegrationTest {
// ...
}

Annotation @TestPropertySource​

@SpringBootTest
@TestPropertySource(properties = {
"spring.jpa.show-sql=false",
"app.feature.enabled=true"
})
class OverriddenPropertiesTest {
// ...
}

5. @Transactional dans les Tests​

Rollback Automatique​

@SpringBootTest
@Transactional // ← Rollback après chaque test
class TransactionalTest {

@Autowired
private UserRepository userRepository;

@Test
void test1() {
userRepository.save(new User("John"));
assertEquals(1, userRepository.count());
}

@Test
void test2() {
// La BDD est vide car test1 a été rollback
assertEquals(0, userRepository.count());
}
}

@DataJpaTest est @Transactional par Défaut​

@DataJpaTest  // ← Déjà @Transactional
class RepositoryTest {
// Rollback automatique après chaque test
}

6. Test avec Données Initiales​

@Sql​

@SpringBootTest
@Sql("/test-data.sql") // ← Exécute avant chaque test
class SqlDataTest {
// ...
}

@Test
@Sql(scripts = "/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
void testWithCleanup() {
// ...
}

@SqlGroup​

@SpringBootTest
@SqlGroup({
@Sql("/schema.sql"),
@Sql("/test-data.sql")
})
class MultiSqlTest {
// ...
}

Fichier test-data.sql​

-- src/test/resources/test-data.sql
INSERT INTO users (id, name, email) VALUES (1, 'John', 'john@test.com');
INSERT INTO users (id, name, email) VALUES (2, 'Jane', 'jane@test.com');

INSERT INTO products (id, name, price) VALUES (1, 'Laptop', 1000.00);

7. TestContainers (Bonus)​

Pourquoi?​

H2 est pratique mais différent de PostgreSQL/MySQL. TestContainers lance de vraies bases de données dans Docker.

Configuration​

<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

Utilisation​

@SpringBootTest
@Testcontainers
class PostgreSQLIntegrationTest {

@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");

@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}

@Autowired
private UserRepository userRepository;

@Test
void shouldSaveToRealPostgres() {
User user = userRepository.save(new User("John", "john@test.com"));

assertNotNull(user.getId());
}
}

8. Slices de Test Spring Boot​

Récapitulatif​

AnnotationContexte ChargéUtilisation
@SpringBootTestToutTests d'intégration complets
@DataJpaTestJPA + H2Tests de repository
@WebMvcTestMVC + SecurityTests de controller
@JsonTestJacksonTests de sérialisation
@RestClientTestRestTemplateTests de clients REST

Combiner avec @MockBean​

@DataJpaTest
@Import(UserService.class) // Importer le service
class UserServiceWithRealDbTest {

@Autowired
private UserService userService;

@MockBean
private EmailService emailService; // Mock externe

@Test
void shouldSaveUser() {
// userService utilise le vrai repository
// emailService est mocké
}
}

9. Résumé​

Annotations Clés​

AnnotationUsage
@SpringBootTestContexte complet
@DataJpaTestTests JPA/Repository
@WebMvcTestTests Controller
@MockBeanMock dans le contexte Spring
@ActiveProfilesProfil de test
@SqlDonnées SQL
@TransactionalRollback automatique

Choix du Type de Test​

┌─────────────────────────────────────────────┐
│ Que tester? │
├──────────────────────┬──────────────────────┤
│ Repository │ @DataJpaTest │
│ Controller seul │ @WebMvcTest │
│ Service avec mocks │ @ExtendWith(Mockito) │
│ Stack complète │ @SpringBootTest │
│ Avec vraie BDD │ TestContainers │
└──────────────────────┴──────────────────────┘

🎯 Prochaine Étape​

Vous savez tester les repositories et services. Maintenant, apprenons à tester l'API REST en détail avec MockMvc.

➡️ Chapitre 5: Tests REST API


⬅️ Retour au Module 9