Test dans Spring Boot

1. Vue d'ensemble

Dans ce didacticiel, nous examinerons l' écriture de tests à l'aide du support du framework dans Spring Boot. Nous couvrirons 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 notre introduction à Spring Boot.

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 ressource d' employé . Il s'agit d'une architecture à plusieurs niveaux typique - l'appel d'API est traité du contrôleur au service vers la couche de persistance .

3. Dépendances de Maven

Ajoutons d'abord nos dépendances de test:

 org.springframework.boot spring-boot-starter-test test 2.2.6.RELEASE   com.h2database h2 test 

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

Le H2 DB est notre base de données en mémoire. Il é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ée Employee, qui a un identifiant et un nom 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) fournit 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 sera 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
  • configuration Hibernate, Spring Data et DataSource
  • exécution d'un @EntityScan
  • activation de la journalisation SQL

Pour effectuer des opérations DB, nous avons besoin de certains enregistrements déjà dans notre base de données. Pour configurer ces données, nous pouvons utiliser TestEntityManager.

Le Spring Boot TestEntityManager est une alternative au JPA EntityManager standard qui fournit des méthodes couramment utilisées lors de l'écriture de tests.

EmployeeRepository est le composant que nous allons tester.

Écrivons maintenant 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 TestEntityManager pour insérer un employé dans la base de données et le lire via l'API find by name.

La partie assertThat (…) provient de la bibliothèque Assertj, fournie avec Spring Boot.

5. Se moquer avec @MockBean

Notre code de couche de service dépend de notre référentiel .

Cependant, pour tester la couche de service , 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 être en mesure d'écrire et de tester notre code de couche de service sans câblage dans notre couche de persistance complète.

Pour y parvenir, nous pouvons utiliser le support de simulation fourni par Spring Boot Test.

Jetons d'abord un coup d'œil au 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 classe Service , nous devons avoir une instance de la classe Service créée et disponible en tant que @Bean afin que nous puissions la @Autowire dans notre classe de test. Nous pouvons réaliser cette configuration en utilisant l' annotation @TestConfiguration .

Lors de l'analyse des composants, nous pouvons constater que des composants ou des configurations créés uniquement pour des tests spécifiques sont accidentellement récupérés partout. Pour éviter cela, Spring Boot fournit l' annotation @TestConfiguration que nous pouvons ajouter aux classes dans src / test / java pour indiquer qu'elles ne doivent pas être détectées par analyse.

Une autre chose intéressante ici est l'utilisation de @MockBean . Il crée un Mock pour EmployeeRepository , qui peut être utilisé pour contourner l'appel vers le EmployeeRepository réel :

@Before public void setUp() { Employee alex = new Employee("alex"); Mockito.when(employeeRepository.findByName(alex.getName())) .thenReturn(alex); }

La configuration étant terminée, le cas de test sera plus simple:

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

6. Tests unitaires avec @WebMvcTest

Notre contrôleur dépend de la couche Service ; 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 ne nous concentrons que sur le code du contrôleur , il est naturel de se moquer du code de la couche Service 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 les contrôleurs , 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. Nous pouvons également l'utiliser avec @MockBean pour fournir des implémentations simulées pour toutes les dépendances requises.

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

Cela dit, écrivons notre cas 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 la méthode get (…) peut être remplacé par d'autres méthodes correspondant aux verbes HTTP comme put () , post () , etc. Veuillez noter que nous définissons également le type de contenu dans la requête.

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

7. Test d'intégration avec @SpringBootTest

Comme son 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'aucune moquerie n'est impliquée.

Idéalement, nous devrions garder les tests d'intégration séparés des tests unitaires et ne pas fonctionner avec les tests unitaires. Nous pouvons le faire en utilisant un profil différent pour exécuter uniquement les tests d'intégration. Plusieurs raisons à cela peuvent être que les tests d'intégration prennent du temps et peuvent nécessiter une base de données réelle pour s'exécuter.

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 est utile lorsque nous devons amorcer l'ensemble du conteneur. L'annotation fonctionne en créant le ApplicationContext qui sera utilisé dans nos tests.

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

Ensuite, l' annotation @TestPropertySource permet de configurer les emplacements des fichiers de propriétés spécifiques à nos tests. Notez que le fichier de propriétés chargé avec @TestPropertySource remplacera le fichier application.properties existant .

The application-integrationtest.properties contains the details to configure the persistence storage:

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

If we want to run our integration tests against MySQL, we can change the above values in the properties file.

The test cases for the integration tests might look similar to the Controller layer unit tests:

@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"))); }

The difference from the Controller layer unit tests is that here nothing is mocked and end-to-end scenarios will be executed.

8. Auto-Configured Tests

One of the amazing features of Spring Boot's auto-configured annotations is that it helps to load parts of the complete application and test-specific layers of the codebase.

In addition to the above-mentioned annotations, here's a list of a few widely used annotations:

  • @WebFluxTest: We can use the @WebFluxTest annotation to test Spring WebFlux controllers. It's often used along with @MockBean to provide mock implementations for required dependencies.
  • @JdbcTest: We can use the @JdbcTest annotation to test JPA applications, but it's for tests that only require a DataSource. The annotation configures an in-memory embedded database and a JdbcTemplate.
  • @JooqTest: To test jOOQ-related tests, we can use @JooqTest annotation, which configures a DSLContext.
  • @DataMongoTest: To test MongoDB applications, @DataMongoTest is a useful annotation. By default, it configures an in-memory embedded MongoDB if the driver is available through dependencies, configures a MongoTemplate, scans for @Document classes, and configures Spring Data MongoDB repositories.
  • @DataRedisTestmakes it easier to test Redis applications. It scans for @RedisHash classes and configures Spring Data Redis repositories by default.
  • @DataLdapTest configures an in-memory embedded LDAP (if available), configures a LdapTemplate, scans for @Entry classes, and configures Spring Data LDAP repositories by default.
  • @RestClientTest: We generally use the @RestClientTest annotation to test REST clients. It auto-configures different dependencies such as Jackson, GSON, and Jsonb support; configures a RestTemplateBuilder; and adds support for MockRestServiceServer by default.

9. Conclusion

In this article, we took a deep dive into the testing support in Spring Boot and showed how to write unit tests efficiently.

Le code source complet de cet article se trouve à l'adresse over sur GitHub. Le code source contient de nombreux autres exemples et divers cas de test.

Et si vous souhaitez continuer à vous renseigner sur les tests, nous avons des articles séparés liés aux tests d'intégration et aux tests unitaires dans JUnit 5.