TP JUnit avec le projet java-springboot-actor-junit existant
Effectuez quelques tests sur une application simple
- Importez le projet maven dans votre Eclipse ou autre. (pour les tests, peu importe la partie BD)
- Vous n’êtes pas obligé.e de tout lire, ni de connaître les méthodes, mais de juste faire les tests.
- Effectuez un update du projet
- Passez au dernier chapitre
- Complètez les tests unitaires dans la classe ActorJdbcTest
- Installez le plug-in de couverture Eclemma dans votre IDE.
- Lancez les Tests Unitaires avec JUnit puis Coverage As
Dans cette application, il n’y a pas d’utilisation du framework Hibernate.
Présentation de l’application SpringBoot/JDBC
Ce projet est une application SpringBoot basique permettant de gérer une base de données d’acteurs avec les opérations CRUD standards. Il utilise la base de données de test Sakila installée avec Mysql. Personnellement, je ne l’installe pas. Peu importe car pour les tests avec JUnit vous n’en aurez pas besoin !
Côté front : client html javascript natif utilisant les bibliothèques jquery & bootstrap.
Côté back : application Java SpringBoot qui implémente les principes du MVC avec une couche DAO qui communique avec Mysql.
L’objectif de ce tutoriel est de fournir un projet permettant de travailler les aspects suivants :
- Analyser un client REST construit avec SpringBoot
- Comprendre le fonctionnement d’une API REST
- Voir comment un client web utilise une API REST
- Effectuer des tests avec JUnit
Prérequis
Pour faire fonctionner ce projet, l’installation des composants suivants est conseillée :
- Eclipse
- Mysql
- Eclipse
- STS - Spring Tool Suite (plugin Eclipse)
Il est aussi recommandé d’avoir fait l’étude du projet java-springboot-decouverte (accessible ici)
Pour créer ce projet dans Eclipse, faire un import du projet Maven.
Eléments de configuration à étudier
pom.xml
En plus des éléments classiques vus dans le projet java-springboot-decouverte, nous avons ajouté des dépendances vers :
- le système de gestion de base de données JDBC (spring-boot-starter-jdbc)
- le connecteur mysql associé à JDBC (mysql-connector-java)
- la bibliothèque javascript JQuery
- le framework UI bootstrap
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>3.3.7</version>
</dependency>
</dependencies>
Remarque : Il est possible de forcer l’utilisation d’une version d’une des dépendances en ajoutant une balise **
application.properties
Vous trouverez dans ce fichier les informations de configuration pour la base de données et pour le sytème de log.
# connection base
spring.datasource.url=jdbc:mysql://localhost/sakila?useSSL=false
spring.datasource.username=admin
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# log
logging.level.root=INFO
logging.file="c:\logSpring.log"
Remarque : Si l’application ne fonctionne pas, c’est peut-être dû à vos paramètres mysql ?! C’est ici qu’il vous faudra travailler pour corriger le problème.
Code source
Notre code source est organisé selon les principes du MVC (Model View Controller) :
- co.simplon.springboot.actor.model : package contenant les éléments du modèle
- co.simplon.springboot.actor.controller : package contenant les contrôleurs de l’application
- co.simplon.springboot.actor.dao : package contenant les classes dao (gestion de la base de données)
- co.simplon.springboot.actor.service : package contenant les classes métiers (business)
Des éléments relatifs à la Vue sont présents dans le répertoire src/main/resources/static (partie front). Pour nous pas besoin de les utiliser.
Le modèle
Actor.java
Il s’agit de la classe de notre modèle de données. Sa structure correspond à la structure de la table associée dans la base de données. On peut la considérer comme un Bean java standard. Il contient :
- un constructeur par défaut
- Getters et Setters pour tous les attributs de la classe.
La couche DAO
La couche DAO est la couche qui gère la persistance des données. Cette couche apporte les méthodes CRUD classiques pour les classes du modèle associées.
ActorDAO.java
Il s’agit d’une interface java classique qui contient les méthodes CRUD pour créer, modifier, supprimer et retrouver des données de type Actor dans la base de données.
JdbcActorDAO.java
JdbcActorDAO est la classe d’implémentation associée à l’interface ActorDAO.
Elle porte le code capable de produire et exécuter les requêtes SQL nécessaires à la persistance des données de type Actor.
Elle annotée de @Repository permettant au système de résolution des dépendances d’identifier les classes “DAO”.
@Repository
public class JdbcActorDAO implements ActorDAO {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private DataSource datasource;
Pour construire l’attribut datasource, qui gère la connexion avec la base de données, on utilisera la classe JdbcTemplate (fournie par Spring). Ainsi on obtient une connexion automatiquement configurée avec les informations du fichier application.properties.
@Autowired
public JdbcActorDAO(JdbcTemplate jdbcTemplate) {
this.datasource = jdbcTemplate.getDataSource();
}
L’annotation @Autowired délègue à Spring la gestion du cycle de vie de cet attribut passé au constructeur qu’elle précède. Avec cette annotation, on demande à Spring de trouver et instancier la classe fournie en argument de notre constructeur.
Le couche service
C’est la couche métier de l’application.
C’est ici qu’est décrit le comportement des classes (diagramme des séquences), les transactions, les relations entre les classes.
Le service sert de transition, est appelé par le contrôleur et agit sur le modèle.
Bien qu’il fasse partie du modèle, il est placé dans le package service.
ActorService
Dans un exemple aussi simple que celui-ci, la classe ActorService n’a que peu d’intérêt.
On y voit une classe “Passe Plat” qui ne fait que relayer les méthodes de la classe DAO. Pour déclarer son existence à Spring, on la précède de l’annotation @Service
@Service
public class ActorService {
@Autowired
private ActorDAO dao;
Remarque : on voit ici une autre utilisation de l’annotation @Autowired faite sur un attribut de la classe. L’idée est la même : déléguer la gestion du cycle de vie de dao à Spring. En scannant notre code, Spring cherchera la classe la plus adaptée à être associée à cet attribut et appellera son constructeur au bon moment (durant l’exécution de notre programme).
Remarque additionnel : L’attribut dao est une interface. Pour résoudre “l’injection de dépendance” décrite ci-dessus, Spring créera une instance de la JdbcActorDAO qui implémente cette interface qu’il considèrera comme la meilleure classe pour faire le travail (C’est la seul implémentation du projet).
Le contrôleur
ActorController.java
Il s’agit du contrôleur de notre application pour le modèle de donnée Actor. C’est cette classe qui va gérer l’API REST de notre application. Les contrôleurs au sens “Spring” sont repérés par une annotation spéciale
@RestController
@RequestMapping("/api")
public class ActorController {
L’annotation @RestController est une version spécialisée de l’annotation @Controller. Elle gère aussi l’ajout de l’annotation @ResponseBody aux méthodes portant les services du contrôleur.
Le controleur possède un attribut faisant une référence à la classe de service ActorService dont la résolution est faite grâce à l’annotation @Autowired:
@Autowired
private ActorService actorService;
Sont ensuite définies les méthodes de la classe du contrôleur.
@RequestMapping(value = "/actors", method = RequestMethod.GET)
public ResponseEntity<?> getAllActors(){
List<Actor> listActor = null;
try {
listActor = actorService.getAllActors();
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
return ResponseEntity.status(HttpStatus.OK).body(listActor);
}
Toutes les méthodes de notre API sont précédées de l’annotation @RequestMapping qui définira le chemin (fin de l’URL) et la méthode de la requête. Pour plus d’informations sur les différentes manières d’écrire une annotation @RequestMapping , rendez-vous sur http://www.baeldung.com/spring-requestmapping.
Remarque : Il possible de renvoyer directement des objets java en retour de fonction. Ces objets sont alors automatiquement sérialisés et renvoyés comme réponse à la requête associée. Nous avons choisi d’utiliser la classe ResponseEntity fournie par Spring qui permet un plus grand contrôle de la réponse HTTP (gestion des codes http, contenu de requête…).
La vue
Comme cela a déjà été évoqué, les éléments relatifs à la vue sont présents dans le répertoire main/src/resources/static. Il s’agit en fait d’un client html / javascript classique qui sera inclu au projet et déployé sur le serveur. Ce client utilisera l’API REST pour accéder aux données des acteurs via des requêtes Ajax.
Pour étudier ce fonctionnement, consulter les fichiers :
- index.html : fichier html point d’entrée du client pour le client web.
- actor.js : fichier javascript contenant les fonctions du client web.
Remarque : L’API REST que propose notre serveur pourra facilement être utilisés par d’autre clients front. Il serait par exemple possible de l’utiliser via des applications mobiles (android, ios…).
Observations
Il existe des classes qui ont des fonctions définies :
- contrôleur
- service
- entité
Il existe une seule vue :
- elle est en HTML dans un dossier précis
- elle est envoyée une seule fois au client
- elle envoie des requêtes Ajax au serveur
- au retour de la requête, elle met en forme les données
Tout est relié grâce à la magie de Spring et des annotations.
Tester l’API REST
Dans le cadre du développement d’une API REST, il est nécessaire de pouvoir “émuler” les requêtes qu’appelleront les futurs clients front d’une application. Vous pouvez utiliser Postman.
Postman est un client équivalent à CURL qui fournit une interface graphique plus sympathique que Curl. Il permet de créer des collections, sauvegarde les requêtes jouées et peut même aller jusqu’à la création de scénarii de test.
Les liens ci-dessous vous aideront à voir comment il fonctionne :
Dernier chapitre, les Tests à effectuer
Voici les données qui seront utilisées mais non mises en place dans la BD.
insert into `actor`(`actor_id`,`first_name`,`last_name`,`last_update`) values (1,'PENELOPE','GUINESS','2006-02-15 04:34:33');
insert into `actor`(`actor_id`,`first_name`,`last_name`,`last_update`) values (2,'NICK','WAHLBERG','2006-02-15 04:34:33');
insert into `actor`(`actor_id`,`first_name`,`last_name`,`last_update`) values (3,'ED','CHASE','2006-02-15 04:34:33');
insert into `actor`(`actor_id`,`first_name`,`last_name`,`last_update`) values (4,'JENNIFER','DAVIS','2006-02-15 04:34:33');
insert into `actor`(`actor_id`,`first_name`,`last_name`,`last_update`) values (5,'JOHNNY','LOLLOBRIGIDA','2006-02-15 04:34:33');
insert into `actor`(`actor_id`,`first_name`,`last_name`,`last_update`) values (6,'JENNIFER','LAWRENCE','2006-02-15 04:34:33');
Vous devez uniquement remplir les méthodes de tests de la classe suivante : ActorJdbcTest et ensuite lancez JUnit…
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ActorApplication.class)
public class ActorJdbcTest {
static ActorService actorSce;
@Autowired
@Qualifier("jdbcActorDAO")
ActorDAO actorDAO;
@BeforeClass
public static void initActor() throws Exception {
actorSce = new ActorService();
}
@Test
public void testFindOneOk() {
// Instanciez un objet actor
// Affectez lui un objet actor ayant l'identifiant 6
// Verifiez avec un assertEquals si le firstName est bien égale à Jennifer
}
@Test
public void testFindOneKo() {
// Instanciez un objet actor
// Affectez lui un objet actor ayant l'identifiant 999
// Verifiez avec un assertNull que l'actor est bien null !
}
@Test
public void testFindOneBisOk() {
// Instanciez un objet actor
// Affectez lui un objet actor ayant l'identifiant 1
// Utilisez assertThat pour verifier que l'objet actor récupéré est bien une instance de la classe Actor
}
@Test
public void testInsert() {
// Instanciez 2 objets un actor et un actorNew à null
// pour le premier actor, affectez lui un mock en utilisant la méthode privée createMock("Jean", "saisrien")
// pour le second le actorNew, utilisez la méthode insertActor() en lui passant actor en paramètre
// Utilisez assertTrue pour verifier que actorNew est différent de null
}
@Test
public void testUpdate() {
// Instanciez 2 objets un actor et un actorNew à null
// Affectez les valeurs suivantes à actor : Jack Ouille id = 2
// pour le timestamp : actor.setLastUpdate(new Timestamp(System.currentTimeMillis()));
// Initialisez actorNew en utilisant la méthode updateActor() en lui passant actor en paramètre.
// ajoutez 2 assertions, l'une pour vérifier que actorNew n'est pas null
// la seconde pour vérifier que "Jack" est bien le firstName de actorNew.
}
@Test
public void testDelete() {
// Instanciez un objet actor avec le constructeur sans argument
// Déclarez une variable Long id avec la valeur 3
// Utilisez la méthode deleterActor() pour effacer l'actor avec l'id = 3
// essayez de récupérer un actor avec l'id = 3
}
/**
* Méthode privée de création d'un mock
**/
private Actor createMock(String firstName, String lastName) {
Actor mock = new Actor();
mock.setFirstName(firstName);
mock.setLastName(lastName);
mock.setId(new Long(0));
mock.setLastUpdate(new Timestamp(System.currentTimeMillis()));
return mock;
}
}
Voici ce que vous devez obtenir comme résultats :