Test en Spring Boot

Test en démarrage de printemps

1. Vue d'ensemble

Dans cet article, nous allons jeter un œil àwriting tests using the framework support in Spring Boot. Nous aborderons les tests unitaires qui peuvent s'exécuter de manière isolée ainsi que les tests d'intégration qui amorceront le contexte Spring avant d'exécuter les tests.

Si vous êtes nouveau sur Spring Boot, consultez nosintro to Spring Boot.

Lectures complémentaires:

Explorer le Spring TestRestTemplate

Apprenez à utiliser le nouveau TestRestTemplate dans Spring Boot pour tester une API simple.

Read more

Guide rapide sur @RestClientTest dans Spring Boot

Guide rapide et pratique sur l'annotation @RestClientTest dans Spring Boot

Read more

Injecter Mockito se moque des haricots de printemps

Cet article expliquera comment utiliser l’injection de dépendance pour insérer des prototypes Mockito dans Spring Beans à des fins de test unitaire.

Read more

2. Configuration du projet

L'application que nous allons utiliser dans cet article est une API qui fournit des opérations de base sur une ressourceEmployee. Il s'agit d'une architecture à plusieurs niveaux typique - l'appel d'API est traité de la coucheController àService vers la couchePersistence.

3. Dépendances Maven

Ajoutons d'abord nos dépendances de test:


    org.springframework.boot
    spring-boot-starter-test
    test
    2.1.6.RELEASE


    com.h2database
    h2
    test
    1.4.194

Lespring-boot-starter-test est la dépendance principale qui contient la majorité des éléments requis pour nos tests.

LeH2 DB est notre base de données en mémoire. Cela élimine le besoin de configurer et de démarrer une base de données réelle à des fins de test.

4. Test d'intégration avec@DataJpaTest

Nous allons travailler avec une entité nomméeEmployee qui a unid et unname comme propriétés:

@Entity
@Table(name = "person")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Size(min = 3, max = 20)
    private String name;

    // standard getters and setters, constructors
}

Et voici notre référentiel - utilisant Spring Data JPA:

@Repository
public interface EmployeeRepository extends JpaRepository {

    public Employee findByName(String name);

}

C’est tout pour le code de la couche de persistance. Passons maintenant à l’écriture de notre classe de test.

Commençons par créer le squelette de notre classe de test:

@RunWith(SpringRunner.class)
@DataJpaTest
public class EmployeeRepositoryIntegrationTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private EmployeeRepository employeeRepository;

    // write test cases here

}

@RunWith(SpringRunner.class) est utilisé pour fournir un pont entre les fonctionnalités de test Spring Boot et JUnit. Chaque fois que nous utilisons des fonctionnalités de test Spring Boot dans nos tests JUnit, cette annotation est requise.

@DataJpaTest fournit une configuration standard nécessaire pour tester la couche de persistance:

  • configuration de H2, une base de données en mémoire

  • réglage Hibernate, Spring Data et lesDataSource

  • exécution d'un@EntityScan

  • activer la journalisation SQL

Pour effectuer certaines opérations de base de données, nous avons besoin de certains enregistrements déjà configurés dans notre base de données. Pour configurer ces données, nous pouvons utiliserTestEntityManager.The TestEntityManager provided by Spring Boot is an alternative to the standard JPA EntityManager that provides methods commonly used when writing tests.

EmployeeRepository est le composant que nous allons tester. Maintenant, écrivons notre premier cas de test:

@Test
public void whenFindByName_thenReturnEmployee() {
    // given
    Employee alex = new Employee("alex");
    entityManager.persist(alex);
    entityManager.flush();

    // when
    Employee found = employeeRepository.findByName(alex.getName());

    // then
    assertThat(found.getName())
      .isEqualTo(alex.getName());
}

Dans le test ci-dessus, nous utilisons lesTestEntityManager pour insérer unEmployee dans la base de données et le lire via l'API find by name.

La partieassertThat(…) provient duAssertj library fourni avec Spring Boot.

5. Se moquer avec@MockBean

Notre code de coucheService dépend de nosRepository. Cependant, pour tester la coucheService, nous n'avons pas besoin de savoir ou de se soucier de la façon dont la couche de persistance est implémentée:

@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeRepository employeeRepository;

    @Override
    public Employee getEmployeeByName(String name) {
        return employeeRepository.findByName(name);
    }
}

Idéalement, nous devrions pouvoir écrire et tester notre code de couche de service sans câblage dans notre couche de persistance totale.

Pour y parvenir,we can use the mocking support provided by Spring Boot Test.

Examinons d'abord le squelette de la classe de test:

@RunWith(SpringRunner.class)
public class EmployeeServiceImplIntegrationTest {

    @TestConfiguration
    static class EmployeeServiceImplTestContextConfiguration {

        @Bean
        public EmployeeService employeeService() {
            return new EmployeeServiceImpl();
        }
    }

    @Autowired
    private EmployeeService employeeService;

    @MockBean
    private EmployeeRepository employeeRepository;

    // write test cases here
}

Pour vérifier la classeService, nous devons avoir une instance de la classeService créée et disponible en tant que@Bean afin que nous puissions la@Autowire dans notre classe de test. Cette configuration est réalisée en utilisant l'annotation@TestConfiguration.

Lors de l'analyse de composants, il est possible que des composants ou des configurations créées uniquement pour des tests spécifiques soient accidentellement ramassés partout. Pour éviter cela,Spring Boot provides @TestConfiguration annotation that can be used on classes in src/test/java to indicate that they should not be picked up by scanning.

Une autre chose intéressante ici est l'utilisation de@MockBean. Itcreates a Mock pour lesEmployeeRepository qui peuvent être utilisés pour contourner l'appel auxEmployeeRepository réels:

@Before
public void setUp() {
    Employee alex = new Employee("alex");

    Mockito.when(employeeRepository.findByName(alex.getName()))
      .thenReturn(alex);
}

Puisque la configuration est terminée, le scénario de test sera plus simple:

@Test
public void whenValidName_thenEmployeeShouldBeFound() {
    String name = "alex";
    Employee found = employeeService.getEmployeeByName(name);

     assertThat(found.getName())
      .isEqualTo(name);
 }

6. Test unitaire avec@WebMvcTest

NotreController dépend de la coucheService; n'incluons qu'une seule méthode pour plus de simplicité:

@RestController
@RequestMapping("/api")
public class EmployeeRestController {

    @Autowired
    private EmployeeService employeeService;

    @GetMapping("/employees")
    public List getAllEmployees() {
        return employeeService.getAllEmployees();
    }
}

Puisque nous nous concentrons uniquement sur le codeController, il est naturel de se moquer du code de coucheService pour nos tests unitaires:

@RunWith(SpringRunner.class)
@WebMvcTest(EmployeeRestController.class)
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private EmployeeService service;

    // write test cases here
}

Pour tester lesControllers, nous pouvons utiliser@WebMvcTest. Il configurera automatiquement l'infrastructure Spring MVC pour nos tests unitaires.

Dans la plupart des cas, @WebMvcTest sera limité à l'amorçage d'un seul contrôleur. Il est utilisé avec@MockBean pour fournir des implémentations simulées pour les dépendances requises.

@WebMvcTest configure également automatiquementMockMvc qui offre un moyen puissant de tester facilement les contrôleurs MVC sans démarrer un serveur HTTP complet.

Cela dit, écrivons notre scénario de test:

@Test
public void givenEmployees_whenGetEmployees_thenReturnJsonArray()
  throws Exception {

    Employee alex = new Employee("alex");

    List allEmployees = Arrays.asList(alex);

    given(service.getAllEmployees()).willReturn(allEmployees);

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(jsonPath("$", hasSize(1)))
      .andExpect(jsonPath("$[0].name", is(alex.getName())));
}

L'appel de méthodeget(…) peut être remplacé par d'autres méthodes correspondant aux verbes HTTP commeput(),post(), etc. Veuillez noter que nous définissons également le type de contenu dans la demande.

MockMvc est flexible, et nous pouvons créer n'importe quelle requête en l'utilisant.

7. Test d'intégration avec@SpringBootTest

Comme le nom l'indique, les tests d'intégration se concentrent sur l'intégration de différentes couches de l'application. Cela signifie également qu'aucun moqueur n'est impliqué.

Ideally, we should keep the integration tests separated from the unit tests and should not run along with the unit tests. Nous pouvons le faire en utilisant un profil différent pour exécuter uniquement les tests d'intégration. Cela peut s'expliquer par le fait que les tests d'intégration prennent beaucoup de temps et peuvent nécessiter l'exécution d'une base de données réelle.

Cependant, dans cet article, nous ne nous concentrerons pas sur cela et nous utiliserons plutôt le stockage de persistance H2 en mémoire.

Les tests d'intégration doivent démarrer un conteneur pour exécuter les cas de test. Par conséquent, une configuration supplémentaire est requise pour cela - tout cela est facile dans Spring Boot:

@RunWith(SpringRunner.class)
@SpringBootTest(
  SpringBootTest.WebEnvironment.MOCK,
  classes = Application.class)
@AutoConfigureMockMvc
@TestPropertySource(
  locations = "classpath:application-integrationtest.properties")
public class EmployeeRestControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @Autowired
    private EmployeeRepository repository;

    // write test cases here
}

L'annotation@SpringBootTest peut être utilisée lorsque nous avons besoin d'amorcer le conteneur entier. L'annotation fonctionne en créant lesApplicationContext qui seront utilisés dans nos tests.

Nous pouvons utiliser l'attributwebEnvironment de@SpringBootTest pour configurer notre environnement d'exécution; nous utilisons iciWebEnvironment.MOCK - pour que le conteneur fonctionne dans un environnement de servlet simulé.

Nous pouvons utiliser l'annotation@TestPropertySource pour configurer les emplacements des fichiers de propriétés spécifiques à nos tests. Veuillez noter que le fichier de propriétés chargé avec@TestPropertySource remplacera le fichierapplication.properties existant.

Leapplication-integrationtest.properties contient les détails pour configurer le stockage de persistance:

spring.datasource.url = jdbc:h2:mem:test
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect

Si nous voulons exécuter nos tests d'intégration sur MySQL, nous pouvons modifier les valeurs ci-dessus dans le fichier de propriétés.

Les cas de test pour les tests d'intégration peuvent ressembler aux tests unitaires de coucheController:

@Test
public void givenEmployees_whenGetEmployees_thenStatus200()
  throws Exception {

    createTestEmployee("bob");

    mvc.perform(get("/api/employees")
      .contentType(MediaType.APPLICATION_JSON))
      .andExpect(status().isOk())
      .andExpect(content()
      .contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
      .andExpect(jsonPath("$[0].name", is("bob")));
}

La différence avec les tests unitaires de coucheController est qu'ici rien n'est simulé et des scénarios de bout en bout seront exécutés.

8. Tests configurés automatiquement

L'une des fonctionnalités étonnantes des annotations auto-configurées de Spring Boot est qu'elles aident à charger des parties de l'application complète et à tester des couches spécifiques de la base de code.

Outre les annotations mentionnées ci-dessus, voici une liste de quelques annotations largement utilisées:

  • @WebFluxTest– we peut utiliser l'annotation@WebFluxTest pour tester les contrôleurs Spring Webflux. Il est souvent utilisé avec@MockBean pour fournir des implémentations simulées pour les dépendances requises.

  • @JdbcTest – we peut utiliser l'annotation@JdbcTest pour tester les applications JPA, mais c'est pour les tests qui ne nécessitent qu'unDataSource. L'annotation configure une base de données intégrée en mémoire et unJdbcTemplate.

  • @JooqTest – Pour tester les tests liés à jOOQ, nous pouvons utiliser l'annotation@JooqTest, qui configure un DSLContext.

  • @DataMongoTest – Pour tester les applications MongoDB@DataMongoTest est une annotation utile. Par défaut, il configure un MongoDB intégré en mémoire si le pilote est disponible via des dépendances, configure un scanMongoTemplate, pour les classes@Document et configure les référentiels Spring Data MongoDB.

  • Applications@DataRedisTest – makes it easier to testRedis. Il recherche les classes@RedisHash et configure les référentiels Spring Data Redis par défaut.

  • @DataLdapTest – configure unLDAP intégré en mémoire (si disponible), configure unLdapTemplate, recherche les classes@Entry et configure les référentiels Spring DataLDAP par défaut

  • @RestClientTest – we utilise généralement l'annotation@RestClientTest pour tester les clients REST. Il configure automatiquement différentes dépendances telles que la prise en charge de Jackson, GSON et Jsonb, configure unRestTemplateBuilder et ajoute la prise en charge deMockRestServiceServer par défaut.

9. Conclusion

Dans ce didacticiel, nous avons exploré en profondeur le support de test dans Spring Boot et avons montré comment écrire efficacement des tests unitaires.

Le code source complet de cet article peut êtrefound over on GitHub. Le code source contient beaucoup plus d'exemples et de tests divers.

Et, si vous voulez continuer à vous renseigner sur les tests, nous avons des articles séparés liés àintegration tests etunit tests in JUnit 5.