Qu’est ce qu’un mock ?

Le mock est utilisé dans le cadre des tests unitaires d’une application.

On peut considérer qu’un mock est une coquille vide représentant une instance de classe sur laquelle le développeur va définir un comportement spécifique.

Le mock dispose du même contrat d’interface que la classe implémentée, méthodes, accesseurs etc. Le mocking permet de définir des comportements / codes retour de méthode ou de services spécifiques pour vérifier le bon fonctionnement de notre application / isoler le fonctionnement de la classe/méthode, pour faire simple, on ajoute un cerveau à notre coquille de noix qui répond de façon simple.

Stratégie de test :

Idempotence

Lorsque l’on développe des tests unitaires ou d’intégration via @‌mockmvc / @‌jest enchainant plusieurs appels de controleurs, il est important que les tests produisent toujours le même résultat.

Ainsi lorsque l’on teste un service qui appelle une base de données ou un service externe, il est important de ne pas modifier les données et donc de pouvoir rejouer le test autant de fois que souhaité.

 

Exemple : lorsque l’on exécute un test unitaire qui persiste une donnée, la 2ème exécution de celui-ci échouera car la donnée sera déjà présente.

Pour cela nous mockons le comportement de la base de données ou du service partenaire plutôt que de faire un vrai appel. Ainsi lorsque l’on réexécute le test, le mock nous répond toujours OK !

Si on prend l’exemple d’un service permettant de faire une sauvegarde en BDD d’un nouveau client via la méthode save() ( java / spring boot / junit / Mockito) :

				
					@Service
public class ClientsService {

    ClientsRepository clientsRepository;

    public ClientsService(ClientsRepository clientsRepository) {
        this.clientsRepository = clientsRepository;
    }

    public Client ajouteClient(String nom, String prenom) {
        Client client = new Client(nom, prenom);
        return clientsRepository.save(client);
    }
}
				
			
				
					@Repository
public class ClientsRepository // extends JPA Repository /MongoRepo etc  {

    public Client save(Client client) {
        // save pas implémenté
        return client;
    }
}
				
			

Implémentation du test

				
					@ExtendWith(MockitoExtension.class)
public class ClientsServiceTest {

    @Mock
    ClientsRepository clientsRepository;

    @InjectMocks
    ClientsService clientService;

    @Test
    void test_save_cas_nominal(){

        // GIVEN
        Client fakeClient = new Client("fakeNom", "fakePrenom");
        Mockito.when(clientsRepository.save(Mockito.any())).thenReturn(fakeClient);

        // WHEN
        Client nouveauClient = clientService.ajouteClient("Martin","Paul");

        // THEN
        Assertions.assertEquals(fakeClient.getNom() , nouveauClient.getNom());
        Assertions.assertEquals(fakeClient.getPrenom() , nouveauClient.getPrenom());

    }

}
				
			

Explication :

Given / When / Then

Les tests sont découpés en trois parties : 

Le givenCe sont les données d’entrée , dans cette partie on retrouvera également les comportements définis des mocks, par ex, sur l’appel de tel service mocké, je retourne un objet, une liste d’objets ou bien je lève une exception.

Le when : il ne fait en général qu’une ligne et défini l’appel au service ou la méthode que l’on souhaite tester.

Le then : il contient tous les contrôles effectués sur le résultat produit. On peut dans cette section également contrôler le passage dans les différents mock (via @‌verify de mockito par ex) et également le contrôle de la non exécution de certains bouts de code.

Tester les cas aux limites :

Comment se comporte mon code lorsque l’application / webservice / base de données distante ne répond pas, répond une erreur ou est lente?

Il est possible via l’utilisation des mock de déclencher l’envoi d’exception :

				
					// Service
public Client ajouteClient(String nom, String prenom) throws TechnicalException {
        Client client = new Client(nom, prenom);
        try {
            client = clientsRepository.save(client);
        } catch (TimeoutException timeoutException){
            throw new TechnicalException("Erreur lors de la sauvegarde client");
        }
        return client;
    }
}
				
			

Test vérifiant que l’exception est bien catché par le service

				
					@Test
void test_throw_exception() throws TimeoutException {

    // GIVEN
    Mockito.when(clientsRepository.save(Mockito.any())).thenThrow(new TimeoutException());

    // THEN
    Assertions.assertThrows(TechnicalException.class , () -> clientService.ajouteClient("Martin","Paul"));
}
				
			

Variante en vérifiant le contenu du message

				
					@Test
  void test_throw_exception_message() throws TimeoutException {

      // GIVEN
      Mockito.when(clientsRepository.save(Mockito.any())).thenThrow(new TimeoutException());

      // THEN
      try {
          clientService.ajouteClient("Martin", "Paul");
      } catch (TechnicalException technicalException) {
          Assertions.assertEquals("Erreur lors de la sauvegarde client", technicalException.getMessage());
      }

  }
				
			

Supprimer les dépendances externes

Les tests unitaires ne doivent pas dépendre de la disponibilité d’une base de données ou d’un service externe, nous devons être en mesure de jouer l’ensemble des tests sur une PIC (via jenkins par ex) et que le résultat produit soit identique, qu’un partenaire soit disponible ou non. En coupant cette dépendance via les mock, les tests peuvent s’exécuter partout de la même manière (local ou via une plateforme d’intégration continue PIC).

Déviance

Il faut bien cibler ce que l’on souhaite mocker et essayer de réduire au minimum la présence de code mocké dans le cadre d’exécution d’un test. Une déviance consiste à vouloir tout mocker et ne plus tester le fonctionnement d’un service mais d’un mock.

Dans ce test par exemple, le service étant mocké, on ne teste pas l’exécution/comportement du service réel mais celui du mock , cela revient à tester le bon fonctionnement du mock = aucun intérêt !

Les différents frameworks java :

Le langage Java contient de nombreux frameworks pour le mock mais les plus connus sont mockito, powermock et easymock. Chacun de ces frameworks propose des fonctionnalités de mock et spy. Powermock est une extension de mockito et permet par exemple de simuler le comportement de méthodes static ou final (non possible avec mockito).

Guillaume BRESSON

Développeur enthousiaste depuis plus de 10 ans, j’affectionne particulièrement les sujets backend.

Issu d’une formation d’ingénieur, j’ai commencé ma carrière en intégrant des portails intranet / internet puis je me suis spécialisé sur le développement backend autour de Java/Groovy/Spring.

Mes sujets de prédilection vont de la conception d’api REST à la mise en place de tuyauterie back to back. Je considère que le métier de développeur actuel réside principalement dans l’écriture de tests pertinents.

Mon expérience me permet aujourd’hui de partager les bonnes pratiques autour du développement, des tests (Junit / Mockito /MockMvc) et de l’architecture applicative type DDD (Domain Driven Design). 

This website stores cookies on your computer. Cookie Policy