Bonjour à tous,
je vous fais part d'un comportement inattendu que je rencontre dans un projet JAVA :
voici la description en détails de ce cas inattendu :
(01.)Je créée un projet JAVA :
- Dans le fichier de configuration 'pom.xml' : J'intègre spring-boot, spring-data et le connecteur à une BDD de type MariaDB.
- Dans le fichier de propritétés 'application.yml' : J'intègre la connexion à ma BDD.
(02.)Je créée une entité 'Specie' :
Cette entité 'Specie' représentera une espèce (animale ou végétale), avec son id, son nom latin, et son nom commun.
(03.)Je créé un DAO 'SpecieRepository' :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 @Entity public class Specie { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Long id; @Column(unique=true) private String latinName; private String commonName; /** * <b>CONSTRUCTEUR</b><br/> * <b>ARGUMENTS : AUCUN</b><br/> */ public Specie() {} /** * <b>CONSTRUCTEUR</b><br/> * <b>ARGUMENTS : TOUS LES ATTRIBUTS (L'ATTRIBUT 'id' INCLUS)</b><br/> * * @param pId L'attribut 'id' de l'entité. * @param pLatinName L'attribut 'latinName' de l'entité. * @param pCommonName L'attribut 'commonName' de l'entité. */ public Specie(Long pId, String pLatinName, String pCommonName) { ... } /** * <b>CONSTRUCTEUR</b><br/> * <b>ARGUMENTS : TOUS LES ATTRIBUTS (L'ATTRIBUT 'id' EXCLUS)</b><br/> * * @param pLatinName L'attribut 'latinName' de l'entité. * @param pCommonName L'attribut 'commonName' de l'entité. */ public Specie(String pLatinName, String pCommonName) { ... } }
Ce DAO effectuera les opérations de persistance (fonctionnalités CRUD et autres) relatives à l'entité 'Spécie'.
(04.)Je créé une interface 'SpecieService' :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 @Repository public interface SpecieRepository extends JpaRepository<Specie, Long> { ... }
Cette interface 'SpecieService' exposera des traitements métier relatifs à l'entité 'Specie'.
(05.)Je créé une classe d'implémentation 'SpecieServiceImpl' :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 public interface SpecieService extends Service<Specie, Exception> { /** * <b>RECHERCHER UNE ENTITE DANS LES DONNEES PERSISTANTES.</b><br/> * CRITERE DE RECHERCHE : L'ATTRIBUT 'id'<br/> * <br/> * @param pId La valeur à rechercher pour l'attribut 'id' de l'entité. * @return L'entité trouvée. * @throws SpecieIdNotValidException L'attribut 'id' fourni est non valide. * @throws SpecieNotFoundException L'entité recherchée est introuvable. */ @Override public abstract Specie rechercherParId(Long pId) throws SpecieIdNotValidException, SpecieNotFoundException; }
Cette classe d'implémentation 'SpecieServiceImpl' réalisera les traitements métier relatifs à l'entité 'Specie'.
(06.)Dans la classe 'SpecieServiceImpl': la méthode 'rechercherParId' et son implémentation
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 @Service public class SpecieServiceImpl implements SpecieService { private SpecieRepository specieRepository; /** * <b>CONSTRUCTEUR</b><br/> * ARGUMENTS : AUCUN<br/> */ public SpecieServiceImpl () { ... } @Autowired public void setSpecieRepository (final SpecieRepository pSpecieRepository) { ... } @Override public Specie rechercherParId(Long pId) throws SpecieIdNotValidException, SpecieNotFoundException { ...[code fourni au paragraphe (06.)]... }
Cette méthode effectue les 2 tâches suivantes:
- Une recherche par id : en appelant la méthode 'findById' de l'objet 'specieRepository' (attribut de cette classe).
- La gestion des erreurs relatives à cette recherche : en traitant tous les cas d'erreur possibles.
(07.)Je créé une classe de test 'SpecieServiceTest' :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 @Override public Specie rechercherParId(Long pId) throws SpecieIdNotValidException, SpecieNotFoundException { Optional<Specie> specieOptional = null; try { specieOptional = specieRepository.findById(pId); } catch (IllegalArgumentException e) { throw new SpecieIdNotValidException(FUNCTIONALITY__FIND_BY_ID + " -- " + ERROR__PROVIDED_ID__NOT_VALID); } if (!specieOptional.isPresent()) { throw new SpecieNotFoundException(FUNCTIONALITY__FIND_BY_ID + " -- " + ERROR__SPECIE_TO_SEARCH__NOT_FOUND); } Specie specieTrouvee = specieOptional.get(); return specieTrouvee; }
Cette classe de test réalisera les tests unitaires sur la classe métier 'SpecieServiceImpl'.
(08.)Dans la classe 'SpecieServiceTest': la méthode 'testRechercherById_DataNonValid' et son implémentation
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 @RunWith(MockitoJUnitRunner.class) public class SpecieServiceTest { private static final Logger LOGGER = LoggerFactory.getLogger(SpecieServiceTest.class); @Mock private SpecieRepository specieRepositoryMock; @InjectMocks SpecieService specieService = new SpecieServiceImpl(); private List<Specie> species = new ArrayList<Specie>(); private int speciesSize; /** * <b>CONSTRUCTEUR</b><br/> * ARGUMENTS : AUCUN<br/> */ public SpecieServiceTest () {} /** * <b>TACHES EFFECTUEES :</b><br/> * ->ALIMENTER LA LISTE LOCALE DES ESPECES.<br/> */ @Before public void setUp() { ... } /** * <b>TEST SUR LA FONCTIONNALITE SUIVANTE :</b><br/> * ->RECHERCHER UNE ESPECE.<br/> * ->CRITERE DE RECHERCHE : L'ATTRIBUT 'ID'.<br/> * <b>CAS DE TEST SUIVANT :</b><br/> * ->L'ID FOURNI EST NON VALIDE.<br/> */ @Test public void testRechercherById_DataNonValid() { ...[code fourni au paragraphe (08.)]... } }
Cette méthode effectue le test décrit ci-dessous :
- Une recherche par id : En appelant la méthode 'rechercherParId' de l'objet 'specieService' (attribut de cette classe).
- Le cas d'erreur provoqué : L'argument 'pId' passé à la méthode 'rechercherParId' est à 'null'.
- La gestion des erreurs : En traitant de manière différenciée le cas d'erreur attendu (l'argument 'id' non valide) des cas d'erreur non attendus.
(09.)L'exécution du test : Le déroulement observé et le déroulement attendu
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 @Test public void testRechercherParId_DataNonValid() { Mockito.when(this.specieRepositoryMock.findById(0L)).thenReturn(Optional.of(this.findSpecieById(0L))); try { this.specieService.rechercherParId(null); Assert.fail("L'exception attendue n'a pas été lancée."); } catch (SpecieIdNotValidException e) { assertTrue("L'exception attendue a été lancée.", true); } catch (Throwable t) { Assert.fail("Une exception non attendue a été lancée."); } }
(09.01.)Le comportement observé est décrit ci-dessous :
- (09.01.A.)Le composant 'SpecieServiceTest', dans sa méthode 'testRechercherParId_DataNonValid' :
Il appelle, dans le service 'SpecieServiceImpl', la méthode 'rechercherParId', avec un argument égal à 'null'.- (09.01.B.)Le service 'SpecieServiceImpl', dans sa méthode 'rechercherParId' :
Il appelle, dans le dao 'SpecieRepository', la méthode 'findById', avec cet argument 'pId' égal à 'null'.- (09.01.C.)Aucune exception n'est lancée par la méthode 'findById'.
La méthode 'findById' s'exécute sans erreur, et renvoie un objet de type Optional<Specie>, qui est non 'null' mais vide.
(09.02.)Le comportement attendu est décrit ci-dessous :
A l'étape (09.01.C.) : La méthode 'findById' aurait dû lancer une 'IllegalArgumentException'.
En effet, la javadoc confirme cela :
Quelqu'un saurait-il expliquer pourquoi cette 'IllegalArgumentException' n'est pas lancée, SVP ?
- Dans la classe 'org.springframework.data.repository.CrudRepository'.
- Pour la méthode 'findById'.
- Si l'argument passé à cette méthode est égal à 'null', alors une 'IllegalArgumentException' est lancée.
La javadoc est-elle incorrecte ?
Toutes vos contributions sont la bienvenue.
Merci d'avance.
chat_roux
Partager