jeudi 30 mai 2024

How to use Testcontainers in SpringBoot?

Introduction

Les tests d'intégration jouent un rôle crucial dans le développement logiciel moderne. Cependant, les tests avec des mocks ou des services en mémoire peuvent ne pas représenter fidèlement les environnements de production, entraînant des bugs inattendus. C'est là que Testcontainers entre en jeu.

Pourquoi avons-nous besoin de Testcontainers ?

Les Défis des Tests Traditionnels

Les tests traditionnels avec des mocks ou des services en mémoire présentent plusieurs limitations, notamment :

  • Incapacité à simuler des environnements de production réels
  • Fonctionnalités sous-jacentes différentes
  • Difficulté à identifier les bugs en environnement de développement

Les Avantages de Testcontainers

Testcontainers permet d'exécuter des tests d'intégration avec des services réels en utilisant des conteneurs Docker, ce qui :

  • Offre un environnement de test plus proche de la production
  • Réduit les bugs en production
  • Améliore la fiabilité des tests

Prérequis pour Réaliser ce TP

Prérequis Techniques

  1. JDK : Utilisez le JDK 21.0.1 ou une version ultérieure. Téléchargez-le ici.
  2. Maven : Utilisez Maven 3.9.7, la dernière version stable. Téléchargez-le ici.
  3. Docker : Utilisez Docker Engine 24.0.9, la dernière version stable. Téléchargez-le ici.

Prérequis de Connaissances

  1. Java
  2. Spring Boot
  3. JUnit
  4. Docker

Installation des Dépendances

Ajoutez les dépendances suivantes à votre fichier pom.xml pour Testcontainers :

xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-testcontainers</artifactId> <version>1.19.8</version> <!-- Utilisez la dernière version de Testcontainers --> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>junit-jupiter</artifactId> <version>1.19.8</version> <!-- Assurez-vous que cette version correspond à celle de Testcontainers --> <scope>test</scope> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <version>1.19.8</version> <!-- Utilisez la version de PostgreSQL correspondante --> <scope>test</scope> </dependency>

Cas de Test

Dans l'exemple ci-dessous, nous essayons de tester une API Rest qui crée et récupère les détails des étudiants. La base de données utilisée pour stocker les données des étudiants est PostgreSQL. Avant de commencer, assurez-vous que Docker est en cours d'exécution.

java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Testcontainers public class StudentControllerTest { @Autowired private TestRestTemplate restTemplate; @Autowired private StudentRepository studentRepository; @Container static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:15-alpine"); @Test public void testAddNewStudent() { // Given Student student = new Student(); student.setName("Alice"); student.setDateOfBirth("02/02/2021"); student.setGrade("II"); // When ResponseEntity<Student> studentResponseEntity = restTemplate. postForEntity("/students", student, Student.class); // Then Student savedStudent = studentResponseEntity.getBody(); assertEquals(HttpStatus.OK, studentResponseEntity.getStatusCode()); assertNotNull(savedStudent); } @Test public void testFetchStudentById() { // Given Student student = new Student(); student.setName("Bob"); student.setDateOfBirth("03/03/2022"); student.setGrade("III"); studentRepository.save(student); // When ResponseEntity<Student> studentResponseEntity = restTemplate. getForEntity("/students/" + student.getId(), Student.class); // Then Student studentResponse = studentResponseEntity.getBody(); assertEquals(HttpStatus.OK, studentResponseEntity.getStatusCode()); assertNotNull(studentResponse); assertEquals(1, studentResponse.getId()); assertEquals(student.getName(), studentResponse.getName()); assertEquals(student.getDateOfBirth(), studentResponse.getDateOfBirth()); assertEquals(student.getGrade(), studentResponse.getGrade()); } }

Exécution des Tests

Avant d'exécuter les tests, assurez-vous que Docker est en cours d'exécution. Utilisez ensuite la commande suivante dans le répertoire racine de votre projet :

bash
./mvnw test

Annotations et Classes Importantes

  • SpringBootTest : Crée un ApplicationContext et exécute l'environnement web entier utilisé pour le test.
  • webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT : Un port disponible est choisi au hasard chaque fois que le test est exécuté.
  • Testcontainers et Containers : Utilisés pour gérer le cycle de vie de démarrage et d'arrêt des conteneurs.
  • TestRestTemplate : De Spring, utilisé pour appeler et tester les points de terminaison REST à travers les cas de test.
  • ServiceConnection : Introduit dans SpringBoot 3.1, utilisé pour enregistrer la connexion de la base de données dans le conteneur au lieu de la mentionner manuellement en utilisant l'annotation DynamicPropertySource.
  • PostgreSQLContainer<>("postgres:15-alpine") : Télécharge la version 15 de PostgreSQL basée sur Alpine Linux comme image de base lorsque le conteneur se lance.

Conclusion

En utilisant Testcontainers, vous pouvez améliorer la fiabilité de vos tests d'intégration en exécutant des services réels dans des conteneurs Docker, créant ainsi un environnement de test plus proche de la production. Cela permet de réduire les bugs qui peuvent survenir en production en s'assurant que vos tests sont exécutés dans un environnement similaire. Testcontainers offre une solution puissante pour tester les intégrations avec des services réels, éliminant les limitations des mocks ou des services en mémoire, et contribuant à une meilleure qualité globale du logiciel. Essayez Testcontainers dans vos projets pour découvrir ses avantages par vous-même.

@DataJpaTest and Repository Class in JUnit | Baeldung
Configuring Separate Spring DataSource for Tests | Baeldung

dimanche 19 mai 2024

Why you should avoid returning lists in API?


En tant que développeurs backend, nous passons une grande partie de notre temps à créer des APIs. 

La création d’APIs flexibles et maintenables est cruciale pour assurer la satisfaction des consommateurs et faciliter les évolutions futures. 

Une pratique courante, mais souvent problématique, consiste à renvoyer directement une liste JSON dans les réponses de l’API

Dans cet article, nous allons examiner pourquoi encapsuler les réponses dans un objet JSON est une bonne pratique et comment cela peut améliorer la qualité de votre API.

Flexibilité et Extensibilité

Facilité d’ajout de nouvelles données

Lorsque vous encapsulez la réponse dans un objet JSON, il est beaucoup plus facile d’ajouter de nouvelles informations sans rompre la compatibilité avec les clients existants

Par exemple, si vous souhaitez ajouter des informations de pagination ou des métadonnées, vous pouvez simplement ajouter de nouveaux champs à l’objet JSON.
Cela évite les problèmes de compatibilité rétroactive.

{
  "students": [
    {
      "name": "Fabrice",
      "level": "Expert"
    },
    {
      "name": "Alice",
      "level": "Junior"
    }
  ],
  "totalElements": 2,
  "totalPages": 1,
  "currentPage": 1,
  "links": {
    "self": "/api/v1/students?page=1",
    "next": "/api/v1/students?page=2"
  }
}

Clarté et Structure

Organisation

En encapsulant les données de réponse dans un objet, vous créez une structure claire et bien définie. Cela facilite la compréhension pour les consommateurs de l’API, car ils peuvent facilement naviguer et extraire les données nécessaires. Cette organisation structurée réduit également les risques d’erreurs lors de l’utilisation de l’API.

Gestion des Erreurs et des Métadonnées

Messages d’erreur

Avec un objet JSON, vous pouvez inclure des messages d’erreur, des codes de statut, et d’autres informations pertinentes directement dans la réponse. Cela permet aux clients de gérer les erreurs de manière plus efficace et d’avoir une meilleure compréhension de ce qui ne va pas.

{
  "students": [],
  "error": {
    "code": 404,
    "message": "No students found"
  }
}

Uniformité et Standardisation

Consistance des réponses

En adoptant un format standard pour toutes les réponses, vous assurez une consistance à travers toute l’API. Les clients savent toujours à quoi s’attendre, ce qui facilite le développement et le débogage. Cette uniformité améliore également la documentation de l’API et la compréhension pour les nouveaux développeurs.

Support des Hypermédias (HATEOAS)

HATEOAS

L’approche RESTful préconise l’utilisation de liens hypermédia pour guider les clients sur les actions possibles. Utiliser un objet JSON facilite l’inclusion de tels liens, permettant aux clients de découvrir et d’interagir avec l’API de manière dynamique.

{
  "students": [
    {
      "name": "Alice",
      "level": "Sophomore",
      "links": {
        "self": "/api/v1/students/1",
        "courses": "/api/v1/students/1/courses"
      }
    }
  ],
  "links": {
    "self": "/api/v1/students",
    "next": "/api/v1/students?page=2"
  }
}

Préparation pour les Futures Évolutions

Évolution de l’API

Les APIs évoluent souvent avec le temps. En encapsulant les réponses dans des objets JSON, vous préparez l’API à évoluer sans casser la compatibilité avec les clients existants

Vous pouvez déprécier certains champs et en introduire de nouveaux progressivement, ce qui facilite la gestion des versions.

Conclusion

Utiliser un objet JSON pour encapsuler les réponses d'une API REST offre de nombreux avantages, notamment en termes de flexibilité, de clarté, de gestion des erreurs, et de support des hypermédias. Cette pratique améliore globalement la qualité, la maintenabilité, et la robustesse de votre API, tout en préparant le terrain pour des évolutions futures. En adoptant cette approche, vous assurez une meilleure satisfaction des consommateurs et une plus grande facilité de développement pour les mainteneurs de l'API. 
Adoptez dès maintenant cette pratique pour vos projets et observez la différence en termes de flexibilité et de maintenabilité de vos APIs REST.

dimanche 5 mai 2024

[Java 21 ] “String Templates” previews additional enhancements (JEP 430)


Introduction

Java 21, la version la plus récente de Java avec un support à long terme, a apporté une multitude de nouvelles fonctionnalités. L’une des plus notables est le “String Templates” ou modèles de chaînes de caractères.

Le défi

Dans de nombreux langages de programmation, l’interpolation de chaînes de caractères est couramment utilisée pour insérer des valeurs dynamiques dans des chaînes de caractères. Cependant, cette méthode peut présenter des risques de sécurité. De plus, la concaténation de chaînes de caractères peut rendre le code difficile à lire et à comprendre. Il existe également d’autres vulnérabilités potentielles liées à l’interpolation de chaînes de caractères, comme les attaques de format de chaîne et les vulnérabilités d’interpolation de chaînes de caractères dans Apache Commons Text.

La composition de chaînes de caractères en Java

En programmation, la composition de chaînes de caractères est une tâche courante. En Java, il existe plusieurs méthodes pour composer des chaînes de caractères, chacune ayant ses propres avantages et inconvénients. Par exemple, la concaténation de chaînes de caractères est la méthode la plus basique pour construire des chaînes de caractères. Cependant, elle peut rendre le code difficile à lire et à maintenir. D’autres méthodes, comme l’utilisation des classes StringBuilder et StringBuffer, ou des formateurs de chaînes de caractères, ont leurs propres défis.

La solution : Les “String Templates”

Pour résoudre ces problèmes, Java 21 introduit les “String Templates”. Ces modèles permettent d’insérer des valeurs dans des chaînes de caractères tout en assurant leur validation et leur assainissement. Cela rend le code plus sûr et plus facile à lire.

Les “String Templates” ont été introduits dans Java avec plusieurs objectifs en tête :

  • Simplifier le processus d’expression des chaînes de caractères avec des valeurs qui peuvent être compilées au moment de l’exécution.
  • Améliorer la lisibilité des compositions de chaînes de caractères, en surmontant la verbosité associée aux classes StringBuilder et StringBuffer.
  • Surmonter les problèmes de sécurité des techniques d’interpolation de chaînes de caractères que d’autres langages de programmation permettent, en échangeant une petite quantité d’inconvénient.
  • Permettre aux bibliothèques Java de définir une syntaxe de formatage personnalisée du littéral de chaîne de caractères résultant.

Voici quelques exemples d’utilisation des “String Templates” avec différents processeurs de template :

// Utilisation du processeur de template STR pour une salutation personnalisée
String prenom = "Alice";
String salutation = STR."Bonjour, \\{prenom} ! Comment ça va ?";
System.out.println(salutation);  // Affiche : Bonjour, Alice ! Comment ça va ?

// Utilisation du processeur de template FMT pour formater un nombre décimal
double pi = 3.14159;
String piFormat = FMT."La valeur de pi arrondie à deux décimales est \\{%.2f pi}";
System.out.println(piFormat);  // Affiche : La valeur de pi arrondie à deux décimales est 3.14

// Utilisation du processeur de template RAW pour échapper les caractères spéciaux
String texte = "Ceci est un texte avec des caractères spéciaux : \\{, \\}, \\";
String texteEchappe = RAW."\\{texte}";
System.out.println(texteEchappe);  // Affiche : Ceci est un texte avec des caractères spéciaux : \\{, \\}, \\

Discussion

Les “String Templates” sont une fonctionnalité puissante, mais elles sont encore en prévisualisation et pourraient être modifiées dans les versions futures de Java. Cependant, elles ont le potentiel d’améliorer la lisibilité et la sécurité du code Java.

String Templates in Java 21 | Baeldung