Les tests unitaires, comme leur nom l’indique, vont tester une unité (un composant) développée indépendamment du reste du programme. Ils permettent de vérifier si le résultat est celui attendu. Une unité testée doit être isolée, si elle dépend d’autres unités, il sera possible de mocker (simuler) ces dernières.
Il est recommandé d’écrire les tests en même temps que le code et de les rejouer à chaque ajout ou modification du code pour éviter les régressions.
Une bonne couverture de tests permet d’être sûr que les fonctionnalités développées fonctionnent bien avant la livraison, mais aussi de vérifier que le code développé sur une version précédente s’exécute toujours correctement sur la version courante.
Cela assure donc la maintenabilité de votre code entre chaque nouvelle version et met en évidence les cas de régression avant la mise en production.
Dans notre cas, nous allons vous présenter comment écrire des tests unitaires en java springboot.
Passons au côté pratique ! Comment écrire des tests unitaires en Java Springboot ? Nous vous détaillons ci-dessous avec un exemple concret, la procédure à suivre pour les dépendances, et pour créer une classe de tests.
Commençons par les dépendances utiles à l’écriture de nos tests, JUnit et PostgreSQL. JUnit est le framework utilisé pour l’écriture de tests, s’ajoute à celui-ci Mockito qui permet de simuler les dépendances des unités testées pour les isoler.
Nous utiliserons dans notre exemple testcontainers, une bibliothèque JavaLangage de développement très populaire ! qui prend en charge les tests JUnit et fournit des instances de BDD pouvant s’exécuter dans un conteneur Docker.
Ajout de JUnit et PostgreSQLMoteur de gestion de base de données libre de droit. (pour la BDD) au pom.xml
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.15.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.15.3</version>
<scope>test</scope>
</dependency>
</dependencies>
Dans l’arborescence de votre projet doit se trouver un dossier « src/test/java », à l’intérieur de celui-ci nous créons un nouveau dossier «controller», dans lequel tous les tests relatifs à nos contrôleurs seront ajoutés.
Créons une nouvelle classe «UserContollerTest» et une méthode de test.
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlConfig;
import org.springframework.test.context.jdbc.SqlGroup;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
@SqlGroup({
@Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = { "classpath:datasets/integration/integration_test_before.sql"}, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)),
@Sql(executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, scripts = { "classpath:datasets/integration/integration_test_after.sql"}, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED))})
class UserControllerIT {
private static final String ENDPOINT = "/api/v1/users/";
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Mock
private UserRepository userRepository;
@InjectMocks
private UserServiceImpl userService;
@BeforeEach
public void init() throws Exception {
User user = new User();
this.mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build();
}
/**
* Test getUserByLastname()
*
*@throws Exception
*/
@Test
void getUser_whenLastnameExistInBDD_shouldReturnAUserAndHttpStatusOk() throws Exception {
String lastname = "Dupont";
mockMvc .perform(addAuthorizationBearerToken(get(new StringBuilder(ENDPOINT)
.append(lastname).toString())))
.andExpect(status().isOk())
.andExpect(jsonPath("lastname").value(lastname))
.andExpect(jsonPath("firstname").value("Eric"));
}
@AfterEach
public void afterTest() throws Exception {
SecurityContextHolder.clearContext();
}
}
Détaillons ensemble cette classe.
Elles vont nous permettre d’ajouter deux fichiers SQLLangage permettant de communiquer avec une base de données.. L’un sera exécuté avant les tests pour insérer des données de test, et l’autre, après les tests pour supprimer ces données qui ne nous serviront plus.
Une fois tous les tests unitaires créés, s’ils s’exécutent correctement, il est possible ensuite d’écrire des tests d’intégration et vérifier la communication entre composants.
L’écriture des tests unitaires en Java Springboot, et pour n’importe quelle stack technique, a un coût pour un projet d’application. Cependant, retenez qu’une classe qui possède un ensemble complet de tests unitaires aura bien moins de chances d’avoir des effets de bords suite à des modifications dans le code lors de corrections ou évolutions, puisqu’ils permettent de s’assurer de la non-régression des fonctionnalités déjà développées. Donc finalement, on s’y retrouve côté coûts :)
Et vous, vous mettez en place des tests unitaires dans vos projets Java Springboot ?
On vous explique le cycle de vie et la configuration des beans sur Java Spring
Migrer un projet Spring Boot vers Quarkus : nous avons testé ! Découvrez notre retour d'expérience en détails dans cet article du blog AXOTALKS.
Le lexique du bug et du débug : tous les termes et méthodes pour comprendre comment débugguer efficacement une application ou un programme informatique.