Formation BACKEND TRINITY

Rappel
Backend
Frontend




+
+

Trinity

Resource oriented architecture (ROA) & rest
REST est à ROA ce que SOAP est à SOA
Les concepts sont issus de la thèse de doctorat en philosophie de M. Roy T. Fielding en 2000...
C'était aussi mon sujet d'examen probatoire au Cnam...
a retenir
Tout est ressource !
principes fondamentaux :
- l'identification des ressources : URI, URL, URN
- la manipulation des ressources au travers de leur représentation, en particulier via le protocole HTTP
- la description des ressources : MIME
...
FOCUS HTTP

FOCUS HTTP
@RequestMapping(value="/customer/{id}" ,headers = "Accept=application/json","application/xml")
public ResponseEntity<Customer> getCustomerById(@PathVariable String id) {
Customer customer;
try {
customer = customerService.getCustomerDetail(id);
} catch (CustomerNotFoundException e) {
return new ResponseEntity<Customer>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<Customer>(customer,HttpStatus.OK);
}
Exemple :
RECOMMANDATIONS
dans confluence...
Il y a aussi les fiches pratiques...
BACKEND

BACKEND

BACKEND
Ce qui nous intéresse plus particulièrement :

Spring Boot
Support de containers embarqués pour la production rapide d'applications auto-exécutables.

Spring CLOUD

Spring WEB
Support REST pour la création d'APIs RESTful
BACKEND
Ce qui nous intéresse plus particulièrement (suite et fin) :
Spring DATA
Data Relational Access

RELATED MODULES
Let's GET STARTED !
premiere Application
- Ouvrir eclipse
- [File] --> [New] --> [Spring Starter Project]

premiere Application
3. Renseigner les caractéristiques du projet

premiere Application
4. Choisir les fonctionnalités Spring :

5. Clic sur [Finish]
4a. développer [Web]
4b. cocher [Web]
premiere Application
6. Lancer l'application :
6.a Clic droit sur le projet, [Run As] --> [Spring Boot App]
ou
6.b Lancement depuis un terminal, à la racine du projet :
mvn spring-boot:run
ou
6.c Packaging et utilisation de la commande java :
mvn clean verify
java -jar ./target/{$artifactId}-{$version}.jar
premiere Application
Mais elle ne fait rien !?
premier CONTROLLER
1. Créer un package
2. Créer une classe HelloController
/**
*
*/
package nc.gouv.dtsi.etudes.axi.formation.hello;
import org.springframework.http.HttpEntity;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @author didier
*
*/
@RestController
public class HelloController {
private static final String hello = "Hello World !";
@RequestMapping(value = "/api/v1/hello",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<String> sayHello() {
return new HttpEntity<String>(hello);
}
}


premier CONTROLLER

@RestController
Depuis Spring 4
Equivaut dans les version précédentes à la combinaison des annotations :
- @Controller
- @ResponseBody
C'est une version spécialisée du Controller et elle doit être utilisée pour la création d'APIs RESTful.
premier CONTROLLER
@RequestMapping
Permet de mapper les requêtes HTTP vers les méthodes concernées des controllers.
value
déclare le chemin à utiliser dans l'URL pour appeler la méthode concernée.
Peut être utilisé au niveau de la méthode ou au niveau de la classe. Dans ce dernier cas, toutes les méthodes de la classe hériteront de ce chemin.

premier CONTROLLER
@RequestMapping
method
Restreint la méthode HTTP :
GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE
Peut être utilisé au niveau de la méthode ou au niveau de la classe. Dans ce dernier cas, toutes les méthodes de la classe hériteront de cette restriction.

premier CONTROLLER
@RequestMapping
produces
Définit le(s) type(s) de media que peut produire la requête. Peut contenir des valeurs multiples :

(...)
produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }
(...)
L'expression peut être négative :
(...)
produces = !MediaType.APPLICATION_XML_VALUE
(...)
Dans ce cas, toutes les requêtes acceptant autre chose que du XML seront acceptées.
Peut être utilisé au niveau de la méthode ou au niveau de la classe. Dans ce dernier cas, la déclaration au niveau de la méthode surchargera la déclaration au niveau de la classe.
premier CONTROLLER
- Lancer l'application
- Tester :
- via CURL :
curl -i http://localhost:8080/api/v1/hello
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Content-Length: 13
Date: Sun, 12 Feb 2017 21:46:21 GMT
Hello World !
- via Postman :

premier CONTROLLER
@RequestMapping avec variable(s)
Modifier la classe HelloController comme suit :

(...)
@RestController
@RequestMapping("/api/v1")
public class HelloController {
private static final String helloToSomeone = "Hello ";
private static final String helloWorld = helloToSomeone + "World !";
@RequestMapping(value = "/hello",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
(...)
@RequestMapping(value = "/hello/{someone}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<String> sayHelloToSomeone(@PathVariable("someone") final String someone) {
return new HttpEntity<String>(helloToSomeone + someone + " !");
}
}
premier CONTROLLER
@RequestMapping avec variable(s)
- Relancer l'application
- Tester :
- via CURL :

curl -i http://localhost:8080/api/v1/hello/Donald
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Content-Length: 14
Date: Sun, 12 Feb 2017 22:51:50 GMT
Hello Donald !
- ou via Postman...
premier CONTROLLER
@RequestMapping avec variable(s)


Si le nom de l'argument de la méthode correspond exactement au nom de la @PathVariable, il est possible de simplifier comme suit :
(...)
@RestController
@RequestMapping("/api/v1")
public class HelloController {
(...)
@RequestMapping(value = "/hello/{someone}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<String> sayHelloToSomeone(@PathVariable final String someone) {
return new HttpEntity<String>(helloToSomeone + someone + " !");
}
}
premier CONTROLLER
@RequestMapping avec variable(s)

Il est possible d'avoir plusieurs @PathVariable
Il est possible d'utiliser les expressions régulières pour contrôler le format de la @PathVariable
premier CONTROLLER
@RequestMapping avec variable(s)

Autre méthode avec @RequestParam
Modifier la méthode sayHelloToSomeone comme suit :
@RequestMapping(value = "/hello",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<String> sayHelloToSomeone(@RequestParam("someone") final String someone) {
return new HttpEntity<String>(helloToSomeone + someone + " !");
}
Relancer l'application...
premier CONTROLLER
@RequestMapping avec variable(s)



premier CONTROLLER
@RequestMapping avec variable(s)

org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'requestMappingHandlerMapping'
defined in class path resource
[org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]:
Invocation of init method failed;
nested exception is java.lang.IllegalStateException:
Ambiguous mapping. Cannot map 'helloController' method
premier CONTROLLER
@RequestMapping avec variable(s)

Premier exercice : corriger la classe HelloController pour continuer à avoir les deux services opérationnels :
curl -i http://localhost:8080/api/v1/hello
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Content-Length: 13
Date: Mon, 13 Feb 2017 00:05:00 GMT
Hello World !
curl -i http://localhost:8080/api/v1/hello?someone=Donald
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Content-Length: 14
Date: Mon, 13 Feb 2017 00:07:33 GMT
Hello Donald !
premier CONTROLLER
@RequestMapping avec variable(s)

Corrigé :
package nc.gouv.dtsi.etudes.axi.formation.hello;
import (...)
@RestController
@RequestMapping("/api/v1")
public class HelloController {
private static final String helloToSomeone = "Hello ";
@RequestMapping(value = "/hello",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<String> sayHelloToSomeone(
@RequestParam(name = "someone",
required = false,
defaultValue = "World")
final String someone) {
return new HttpEntity<String>(helloToSomeone + someone + " !");
}
}
PLAY WITH DATA

Spring DATA JPA
Utilisation d'une base de données embarquées basée sur le code officiel géographique de l'INSEE.
Spring DATA JPA
1. Dans src/main/resources, supprimer le fichier application.properties et créer un fichier application.yml
spring:
application:
name: Formation Trinity Backend
jpa:
hibernate:
ddl-auto: none
show-sql: true
database-platform: org.hibernate.dialect.HSQLDialect
datasource:
url: jdbc:hsqldb:mem:testdb
driverClassName: org.hsqldb.jdbcDriver
username: sa
password:
platform: hsqldb
data:
rest:
base-uri: /api
flyway:
enabled: true
Spring DATA JPA
2. Modification du fichier pom.xml du projet, par ajout des dépendances nécessaires :
(...)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<scope>runtime</scope>
</dependency>
(...)
Spring DATA JPA
3. Créer un nouveau package
4a. Création de la classe entité (mappe un objet de la couche de persistence, i.e. en général une table de la base de données)
4b. Création du repository
4c. Création du controller
Spring DATA JPA
Exemple (table T_REGION) :
Entité JPA :
package nc.gouv.dtsi.etudes.axi.formation.cog.region;
import ...;
@Entity
@Table(name = "T_REGION")
public class Region {
@Id
@Column(name = "REGION")
private String codeRegion;
@Column(name = "CHEFLIEU")
private String chefLieu;
@Column(name = "TNCC")
private String typeDeNomEnClair;
@Column(name = "NCC")
private String nomEnClair;
@Column(name = "NCCENR")
private String nomTypoEnrichie;
/*
Accesseurs...
*/
}
Spring DATA JPA
Exemple (table T_REGION) :
Entité JPA :
Ne pas oublier les accesseurs, les constructeurs, etc.
Spring DATA JPA
Exemple (table T_REGION) :
Repository :
package nc.gouv.dtsi.etudes.axi.formation.cog.region;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RegionRepository extends JpaRepository<Region, String> {
}
Spring DATA JPA
Exemple (table T_REGION) :
Controller :
package nc.gouv.dtsi.etudes.axi.formation.cog.region;
import ...;
@RestController
@RequestMapping(path = "/api/v1",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@CrossOrigin(origins = "*")
public class RegionController {
@Autowired
private RegionRepository lRegionRepository;
@RequestMapping(path = "/regions")
public List<Region> getAll() {
return lRegionRepository.findAll();
}
}
Spring DATA JPA
- Lancer l'application
- Tester :
- via CURL
- via Postman
- via le navigateur...
Spring DATA JPA

Spring DATA JPA
Et la magie du framework Spring...
Par exemple pour rechercher par la valeur d'un des attributs de notre entité, par exemple, l'attribut :
/**
* NCCENR - Nom en clair (typographie riche) Libellé en typographie riche,
* majuscules, minuscules, accentuation.
*/
@Column(name = "NCCENR")
private String nomTypoEnrichie;
Spring DATA JPA
1. Modifier l'interface Repository en lui ajoutant :
public interface RegionRepository extends JpaRepository<Region, String> {
Region findByNomTypoEnrichie(final String pCriteria);
}
2. Enrichir le controller de la méthode :
@RequestMapping(path = "/regions/{criteria}")
public ResponseEntity<Region> getByCriteria(
@PathVariable(name = ("criteria"), required = true) final String pCriteria) {
/*
* Default pessimistic response
*/
ResponseEntity<Region> response = new ResponseEntity<>(null,
HttpStatus.NOT_FOUND);
Region result = lRegionRepository
.findByNomTypoEnrichie(pCriteria);
if (result != null) {
response = new ResponseEntity<>(result, HttpStatus.OK);
}
return response;
}
Spring DATA JPA
- Relancer l'application
- Tester :
- via CURL
- via Postman
- via le navigateur...
Spring DATA JPA
Pour aller encore plus loin, nous allons combiner les critères...
1. Remodifier l'interface Repository :
public interface RegionRepository extends JpaRepository<Region, String> {
Region findByCodeRegionOrNomTypoEnrichie(final String pCodeRegion,
final String pNomTypoEnrichie);
}
2. Modifier le controller :
@RequestMapping(path = "/regions/{criteria}")
public ResponseEntity<Region> getByCriteria(
@PathVariable(name = ("criteria"), required = true) final String pCriteria) {
(...)
Region result = lRegionRepository
.findByCodeRegionOrNomTypoEnrichie(pCriteria, pCriteria);
(...)
}
Spring DATA JPA
Attention à la subtilité :
findByCodeRegionOrNomTypoEnrichie(pCriteria, pCriteria)
La valeur du critère de recherche est passée en double pour que la recherche soit faite sur les deux attributs concernés...
Spring DATA JPA
- Relancer l'application
- Tester :
- via CURL
- via Postman
- via le navigateur...
Spring DATA JPA
A VOUS DE JOUER !
Spring DATA REST
Documentation de référence ici
1. Dans src/main/resources, supprimer le fichier application.properties et créer un fichier application.yml
spring:
application:
name: Formation Trinity Backend
jpa:
hibernate:
ddl-auto: none
show-sql: true
database-platform: org.hibernate.dialect.HSQLDialect
datasource:
url: jdbc:hsqldb:mem:testdb
driverClassName: org.hsqldb.jdbcDriver
username: sa
password:
platform: hsqldb
data:
rest:
base-uri: /api
flyway:
enabled: true
Spring DATA REST
2. Modification du fichier pom.xml du projet, par ajout de la dépendance :
(...)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
(...)
Spring DATA REST
Exemple (table T_REGION) :
Entité JPA : cf. ce qui a été fait dans l'exemple Spring Data JPA
Repository :
package nc.gouv.dtsi.etudes.axi.formation.cog.region;
import org.springframework.data.jpa.repository.JpaRepository;
@RepositoryRestResource(collectionResourceRel = "region", path = "regions")
public interface RegionRepository extends JpaRepository<Region, String> {
(...)
}

Spring DATA REST
3. Créer un nouveau package
4a. Création de la classe entité (mappe un objet de la couche de persistence, i.e. en général une table de la base de données)
4b. Création du repository
Spring DATA REST
@RepositoryRestResource
N'est pas obligatoire. Permet de préciser et / ou modifier les détails d'exportation et d'implémentation de l'Interface lors de l'exécution.
Par défaut, c'est le nom de l'entité qui est utilisé pour mapper le endpoint.

Spring DATA REST
Le Repository est donc une interface et les opérations possibles sont définies au niveau de l'Interface étendue, JpaRepository dans notre exemple.
Les différents types de Repository :
- CrudRepository<T, ID extends Serializable>
- PagingAndSortingRepository<T, ID extends Serializable>
- RevisionRepository<T, ID extends Serializable>
- JpaRepository<T, ID extends Serializable>
...
Cf. la javadoc...
Spring DATA REST
Spring Data REST utilise HAL pour formatter la réponse en JSON. Ce format fournit en particulier des informations supplémentaires pour la navigation dans le résultat de la requête.
Spring DATA REST
Différences avec SPRING DATA JPA ?
Spring DATA REST
A VOUS DE JOUER !
Spring HATEOAS
Documentation de référence ici
Pas utilisé à la DTSI !
FOCUS
API SPECIFICATION
Cloner le projet :
https://github.com/DVanderstoken/formation-trinity-backend-final.git
et basculer sur la branche feat/spring-data-jpa
git clone https://github.com/DVanderstoken/formation-trinity-backend-final.git
Clonage dans 'formation-trinity-backend-final'...
remote: Counting objects: 162, done.
remote: Compressing objects: 100% (73/73), done.
remote: Total 162 (delta 36), reused 149 (delta 23), pack-reused 0
Réception d'objets: 100% (162/162), 948.83 KiB | 43.00 KiB/s, fait.
Résolution des deltas: 100% (36/36), fait.
Vérification de la connectivité... fait.
cd formation-trinity-backend-final/
git checkout feat/spring-data-jpa
La branche feat/spring-data-jpa est paramétrée pour suivre la branche distante feat/spring-data-jpa depuis origin.
Basculement sur la nouvelle branche 'feat/spring-data-jpa'
Effectuer des recherches "complexes" :
Rappel de ce qui a été vu :
public interface RegionRepository extends JpaRepository<Region, String> {
Region findByCodeRegionOrNomTypoEnrichie(final String pCodeRegion,
final String pNomTypoEnrichie);
}
Beaucoup de combinaison possible mais lisibilité rapidement limitée du code, en particulier le nom de la méthode...
Mise en oeuvre de l'API Specification :
- Dans le fichier pom.xml du projet :
<build>
<plugins>
(...)
<!-- Used to generate JPA Static Metamodel & Specification API usage -->
<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>process</id>
<goals>
<goal>process</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<processors>
<processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
</processors>
</configuration>
</execution>
</executions>
<configuration>
<outputDirectory>src/main/generated</outputDirectory>
</configuration>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>5.0.2.Final</version>
</dependency>
</dependencies>
</plugin>
(...)
</plugins>
</build>
Mise en oeuvre de l'API Specification :
- Dans l'environnement de développement Eclipse :
1/ clic droit sur le projet concerné ;
2/ clic sur [Properties] ;
3/ Dans la boîte de dialogue des préférences du projet, déplier [Java Compiler], et sélectionner [Annotation processing] :
3.1/ cocher [Enable project specific settings]
3.2/ cocher [Enable annotation processing]
3.3/ cocher [Enable processing in editor]
3.4/ spécifier le dossier dans lequel seront stockées les sources générées
4/ clic sur [OK]
Mise en oeuvre de l'API Specification :
Cette configuration va permettre la génération automatique des classes correspondant au JPA Static Metamodel pour les classes annotées @Entity.
Les classes ainsi créées sont suffixées par '_'.
Mise en oeuvre de l'API Specification :
L'interface Specification<T> définit une seule méthode permettant de créer un prédicat - au sens JPA - et de construire la clause WHERE :
javax.persistence.criteria.Predicate toPredicate(javax.persistence.criteria.Root<T> root,
javax.persistence.criteria.CriteriaQuery<?> query,
javax.persistence.criteria.CriteriaBuilder cb)
Mise en oeuvre de l'API Specification :
Modification de l'interface Repository (exemple) :
package nc.gouv.dtsi.etudes.axi.formation.cog.commune;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface CommuneRepository
extends JpaRepository<CommuneAbregee, CommuneId>,
JpaSpecificationExecutor<CommuneAbregee> {
}
Il faut hériter de l'interface JpaSpecificationExecutor<T> qui amène les méthodes de recherche :
long count(Specification<T> spec)
List<T> findAll(Specification<T> spec)
Page<T> findAll(Specification<T> spec, Pageable pageable)
List<T> findAll(Specification<T> spec, Sort sort)
T findOne(Specification<T> spec)
Mise en oeuvre de l'API Specification :
Les prédicats sont créés de manière unitaire dans une classe dédiée et assemblés lors de l'appel au Repository :
package nc.gouv.dtsi.etudes.axi.formation.cog.commune;
import (...);
public class CommuneSpecification {
public static Specification<CommuneAbregee> isInRegion(final String codeRegion) {
return checkSpec(CommuneAbregee_.codeRegion, codeRegion);
}
public static Specification<CommuneAbregee> isInDepartement(
final String codeDepartement) {
return checkSpec(CommuneAbregee_.codeRegion, codeDepartement);
}
public static Specification<CommuneAbregee> isTheCommune(
final String codeCommune) {
return checkSpec(CommuneAbregee_.codeRegion, codeCommune);
}
private static Specification<CommuneAbregee> checkSpec(
SingularAttribute<Commune, String> propertyName, String content) {
if (StringUtils.trimToNull(content) != null) {
return (root, query, cb) -> cb.equal(root.get(propertyName),
content);
}
return null;
}
}
Mise en oeuvre de l'API Specification :
Les prédicats sont créés de manière unitaire dans une classe dédiée et assemblés lors de l'appel au Repository :
package nc.gouv.dtsi.etudes.axi.formation.cog.commune;
import (...);
@RestController
@RequestMapping(path = "/api/v1")
@CrossOrigin(origins = "*")
public class CommuneController {
@RequestMapping(path = "/communes/search"
, method = RequestMethod.GET
, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Page<CommuneAbregee> search(
@RequestParam(name = "region",
required = false) final String pRegion,
@RequestParam(name = "departement",
required = false) final String pDepartement,
@RequestParam(name = "commune",
required = false) final String pCommune,
Pageable pPageable) {
return lCommuneRepository.findAll(where(isInRegion(pRegion))
.and(isInDepartement(pDepartement)).and(isTheCommune(pCommune)),
pPageable);
}
}
Exécuter l'application et tester...
A vous de jouer !
FOCUS
Externalisation de la configuration
Externalisation de la configuration
cf. src/main/ressources/application.yml
Documentation ici
Première approche à approfondir lors de l'intégration à l'écosystème Trinity Backend (Cf. Spring Cloud).
FOCUS
Flyway
Le fichier pom.xml
Dépendances supplémentaires :
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
</dependency>
<dependency>
<groupId>net.sf.supercsv</groupId>
<artifactId>super-csv</artifactId>
<version>2.4.0</version>
</dependency>

Documentation de référence ici
Outil de migration des bases de données.
- Utilise :
- le langage SQL
- le langage Java
- un mix des deux
- Fournit un client de ligne de commande
- S'intègre facilement avec Spring
Convention over configuration !

Configuration (cf. fichier application.yml) :
flyway:
enabled: true

s'appuie sur la configuration Spring d'accès à le source de données !
spring:
datasource:
url: jdbc:hsqldb:mem:testdb
driverClassName: org.hsqldb.jdbcDriver
username: sa
password:
platform: hsqldb

Les migrations
Cf. répertoires :
- src/main/resources.db/migration (migrations SQL)
- src/main/java/db/migration (migrations Java)

- Convention over configuration
- importance de la gestion des versions
- pas de retour arrière !
A retenir
Les tests
ou comment éviter...


Les tests
Les tests
Adapter au(x) besoin(s) et au contexte...
Cas particulier ici : peu, voire pas de services (couche métier)
Tests par couche !
--> tests de la couche d'accès aux données
--> tests de la couche controllers
Les tests
Exemple sur la couche d'accès aux données :
package nc.gouv.dtsi.etudes.axi.formation.cog.tests;
import (...);
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FormationTrinityBackendApplication.class)
public class RegionRepositoryTest extends FormationTrinityBackendAbstractTest {
@Autowired
private RegionRepository lRegionRepository;
@Test
public void shouldReturnNonEmptyRegionList() {
List<Region> result = lRegionRepository.findAll();
assertFalse(result.isEmpty());
}
}
Les tests
@RunWith(SpringJUnit4ClassRunner.class)
Junit va invoquer la classe spécifier par l'annotation @RunWith pour exécuter les tests. La classe spécifiée SpringJUnit4ClassRunner est une extension JUnit qui fournit à JUnit les fonctionnalités du Spring TestContext Framework.
@SpringBootTest(classes = FormationTrinityBackendApplication.class)
Permet d'exécuter les tests dans le contexte, avec la configuration et dans l'environnement de l'application Spring Boot à laquelle les tests se rapportent.
Les tests
Exemple sur la couche controller :
package nc.gouv.dtsi.etudes.axi.formation.layer.controller;
import (...)
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FormationTrinityBackendApplication.class)
@WebAppConfiguration
public class RegionControllerTest extends FormationTrinityBackendAbstractTest {
private MockMvc lMockMvc;
@Autowired
private WebApplicationContext lContext;
@Before
public void setUp() {
lMockMvc = MockMvcBuilders.webAppContextSetup(lContext).build();
}
@Test
public void shouldReturnNonEmptyRegionListWithHttpCode200()
throws Exception {
lMockMvc.perform(get("/api/v1/regions")).andExpect(status().isOk())
.andExpect(
content().contentType(MediaType.APPLICATION_JSON_UTF8));
}
}
Les tests
@WebAppConfiguration
L'utilisation de cette annotation sur une classe de test signifie que ledit test va être exécuté dans le contexte de l'application (Web).
Les tests
Un peu de factorisation...

Les tests
Ce qu'il y a en commun :
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FormationTrinityBackendApplication.class)
Une proposition ?
Les tests
Un peu de factorisation...
package nc.gouv.dtsi.etudes.axi.formation;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import nc.gouv.dtsi.etudes.axi.formation.FormationTrinityBackendFinalApplication;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = FormationTrinityBackendFinalApplication.class)
public abstract class FormationTrinityBackendAbstractTest {
}
public class RegionRepositoryTest extends FormationTrinityBackendAbstractTest {
(...)
public class RegionControllerTest extends FormationTrinityBackendAbstractTest {
(...)
A vous de jouer !
documenter les api rest
cf. la documentation ici.
documenter les api
Enrichir le fichier pom.xml de l'application avec :
documenter les api
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-data-rest</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-bean-validators</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.6.1</version>
</dependency>
Créer une classe de configuration de la génération de la documentation :
documenter les api
package nc.gouv.dtsi.etudes.axi.formation;
import (...);
@Configuration
@EnableSwagger2
public class ApplicationApiDocConfiguration {
@Autowired
private Environment environment;
/**
* Enabled only if Spring active profiles contains the apidoc profile !
*
* @return a SpringFox Docket
*/
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.enable(Arrays.asList(this.environment.getActiveProfiles())
.contains("apidoc"));
}
}
La documentation des APIs est ici rendue disponible uniquement s'il y a un profile actif - au sens Spring du terme - nommé "apidoc".
La présence de ce profil peut ainsi être gérée en fonction des cycles (intégration, qualification, production, etc.) s'il n'est pas souhaité de la rendre disponible dans un cycle donné.
documenter les api
documenter les api

Formation Backend Trinity
By Didier Vanderstoken
Formation Backend Trinity
- 862