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.
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) {
		...
	}
}
(03.)Je créé un DAO 'SpecieRepository' :

Ce DAO effectuera les opérations de persistance (fonctionnalités CRUD et autres) relatives à l'entité 'Spécie'.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
@Repository
public interface SpecieRepository extends JpaRepository<Specie, Long> {
	...
}
(04.)Je créé une interface 'SpecieService' :

Cette interface 'SpecieService' exposera des traitements métier relatifs à l'entité 'Specie'.
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;
}
(05.)Je créé une classe d'implémentation 'SpecieServiceImpl' :

Cette classe d'implémentation 'SpecieServiceImpl' réalisera les traitements métier relatifs à l'entité 'Specie'.
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.)]...
	}
(06.)Dans la classe 'SpecieServiceImpl': la méthode 'rechercherParId' et son implémentation

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.


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;
	}
(07.)Je créé une classe de test 'SpecieServiceTest' :

Cette classe de test réalisera les tests unitaires sur la classe métier '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
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.)]...
	}
}
(08.)Dans la classe 'SpecieServiceTest': la méthode 'testRechercherById_DataNonValid' et son implémentation

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.


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.)L'exécution du test : Le déroulement observé et le déroulement attendu

(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 :
  • 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.
Quelqu'un saurait-il expliquer pourquoi cette 'IllegalArgumentException' n'est pas lancée, SVP ?
La javadoc est-elle incorrecte ?
Toutes vos contributions sont la bienvenue.
Merci d'avance.
chat_roux