Partage
  • Partager sur Facebook
  • Partager sur Twitter

Problème de compréhension des mocks

Junit, Mockito, Spring boot

    24 mai 2019 à 10:02:22

    Bonjour,

    Je viens vers vous parce que j'ai un problème de compréhension sur mockito et sans doute sur les mocks en général.

    Je cherchais une solution pour tester des classes qui appellent de nombreuses méthodes en évitant d'appeler ces méthodes. Mettons par exemple un service qui appelle un DAO qui fait de très nombreuses opérations sur la base, mais qui doit retourner différents résultats selon ce que renvoie le DAO. Je suis tombé sur Mockito et j'ai cru avoir trouvé la solution, mais ça ne fonctionne pas comme je le pensais.

    Prenons un exemple :

    DaoMini.java (dans src/main)

    package com.domain;
    
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class DaoMini {
    	public boolean MegaMethode(String truc) {
    		System.out.println("Code tellement long à exécuter que la réponse sera 42");
    		return true;
    	}
    }

    ServiceMini.java (dans src/main)

    package com.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import com.domain.DaoMini;
    
    @Service
    public class ServiceMini {
    	@Autowired DaoMini dao;
    	
    	public boolean MethodeService() {
    		return dao.MegaMethode("truc");
    	}
    }
    

    testServiceMini.java (dans src/test)

    package com.service;
    
    import static org.mockito.Mockito.*;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import com.domain.DaoMini;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class testServiceMini {
    
    	@Autowired private ServiceMini serviceMini;
    	
    	@Test
    	public void testMethodeService() {
    		DaoMini dao = mock(DaoMini.class);
    		when(dao.MegaMethode(any(String.class))).thenReturn(false);
    		System.out.println(serviceMini.MethodeService());
    		System.out.println(dao.MegaMethode("truc"));
    	}
    
    }
    

    D'après ce que j'avais compris, le code aurait dû me retourner :

    false
    false

    Pourtant, j'obtiens :

    Code tellement long à exécuter que la réponse sera 42
    true
    false

    Du coup, je ne comprends pas l'intérêt de mockito... Si les mocks ne servent qu'à calculer des données en amont pour fabriquer des objets dans le code de test, autant créer mon objet moi-même sans m'encombrer de tout ça.

    Question subsidiaire : Si je ne fais pas d'erreur et que j'ai effectivement mal compris le fonctionnement des mocks, existe-t-il autre chose qui me permettrait de faire ce que je souhaite ?

    Merci d'avance pour votre aide !

    Edit : J'ai trouvé la réponse à ma question subsidiaire, je la mets ici pour que ça profite à tout le monde !

    testServiceMini.java

    package com.service;
    
    import static org.mockito.Mockito.*;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.MockitoAnnotations;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import com.domain.DaoMini;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class testServiceMini {
    
    	@Before
    	public void init() {
    	    MockitoAnnotations.initMocks(this);
    	}
    	@Mock DaoMini dao;
    	@Autowired @InjectMocks private ServiceMini serviceMini;
    	
    	@Test
    	public void testMethodeService() {
    		when(dao.MegaMethode(any(String.class))).thenReturn(false);
    		System.out.println(serviceMini.MethodeService());
    		System.out.println(dao.MegaMethode("truc"));
    	}
    
    }
    

    Il faut utiliser @InjectMocks pour que les mocks soient bien injectés dans la classe pour laquelle on ne veut pas que des méthodes soient appelées. Il devient possible de faire des tests unitaires sur toutes les méthodes, même le main !

    Mais du coup, je ne comprends pas l'intérêt des mocks à l'intérieur du code de test et je serais reconnaissant si quelqu'un pouvait prendre le temps de m'expliquer.

    Cordialement.


    -
    Edité par LéoZangelmi 24 mai 2019 à 10:42:49

    • Partager sur Facebook
    • Partager sur Twitter
      27 mai 2019 à 9:18:57

      Je me permets de remonter le sujet puisque je n'ai eu aucune réponse en 3 jours. Quelqu'un pourrait-il m'aider à comprendre l'intérêt des mocks en-dehors de la possibilité de les injecter dans des méthodes ? N'est-il pas plus simple de directement créer les objets voulus dans le code de test ?

      -
      Edité par LéoZangelmi 27 mai 2019 à 9:19:09

      • Partager sur Facebook
      • Partager sur Twitter
        30 mai 2019 à 21:58:32

        Bonjour,

        je vois que tu as réussi à résoudre toi même ton problème, c'est un très bon début. Je vais néanmoins essayer de répondre à ta question et en profiter pour te donner quelques conseils.

        Dans une logique de test unitaire, on veut tester uniquement une méthode (dans ton exemple, tu veux tester la méthode de ton service). Or cette méthode dépend bien souvent du résultat d'une autre, ici ton DAO. Pour vérifier que notre méthode fait bien le job, on va supposer que les méthodes qu'elles utilisent fonctionnent bien. C'est la que les mocks interviennent puisqu'on va simuler ces sous méthodes. 

        Ainsi dans ton code, tu indique que la méthode de ton DAO retournera false. A toi ensuite de vérifier que ta méthode (celle que tu test) renvoi également false si le DAO renvoi false. Cette simulation est intéressante, puisque vu que tu n'utilise pas la méthode de ton DAO pour ton test (mais tu la simule), une erreur sur celle-ci n'a pas d'impact sur ton test (ce qui le rend unitaire).

        Ici dans ta clause when tu indique que veux que t'as fonction renvoi false, peu importe ce qu'il y a en entrée, mais tu pourrais très bien faire un test ou ton DAO renvoi false si il reçoit "truc" en entrée et un autre test où il renvoi true si c'est "machin" en entrée.

        when(dao.MegaMethode(eq("truc"))).thenReturn(false);
        when(dao.MegaMethode(eq("machin"))).thenReturn(true);


        Si un when n'est pas utilisé, ton test renverra une erreur, ce qui permet aussi de vérifier qu'on appelle bien la méthode du DAO comme elle le devrait.

        J'espère que mon explication sur les mocks t'as permis d'y voir un peu plus clair. J'avoue ne pas trop savoir si ce que je raconte est toujours compréhensible. N'hésites pas à me dire si ce que j'ai dit n'est pas assez clair.

        Pour finir une remarque plus globale, lors des tests unitaires, tu n'as pas besoin de charger le contexte Spring, celui-ci est plutôt réservé aux tests d'intégration. Si tu veux te renseigner sur le sujet, la doc Spring est très bien (en anglais). Ici à la place du SpringRunner, tu pourrais utiliser la classe MockitoJUnitRunner ce qui réduirait grandement le temps d'exécution de tes tests. De plus, dans ce cas précis, tu n'aurais plus besoin de @Before puisque le runner effectuerais cette action pour toi.

        Enfin j'ose espérer que t'es tests contiennent des assertions et que le log du retour de ta méthode est uniquement présent à des fins de compréhension.

        Pour résumer, le code que tu aurais à la fin, après tout ce que je t'ai raconté :

         
         
        @RunWith(MockitoJUnitRunner.class)
        public class testServiceMini {
         
           @Mock 
           private DaoMini dao;
        
           @InjectMocks 
           private ServiceMini serviceMini;
             
            @Test
            public void testMethodeService() {
                when(dao.MegaMethode(eq("truc"))).thenReturn(false);
                assertFalse(serviceMini.MethodeService());
        
            }
         
        }



        • Partager sur Facebook
        • Partager sur Twitter
          3 juin 2019 à 10:29:32

          Merci beaucoup pour ta réponse. :)

          Je vais commencer par te rassurer, j'utilise bien des assertions ! J'ai juste écrit ce code parce que je savais ce que je voulais obtenir en console si tout fonctionnait comme prévu.

          Tes explications m'aident beaucoup et en te lisant je me rends compte que j'ai mélangé des tests unitaires et des tests d'intégration... Dans le code que je suis en train d'écrire, j'ai fait appeler sans stub certaines méthodes du domain par le service pour tester si la méthode du service déclenche bien l'exécution des opérations voulues. Je ne vois pas comment tester cette méthode du service en unitaire, du coup...

          Pour ce qui est de la clause when, j'ai fait les deux tests thenReturn(true) et thenReturn(false) parce que j'ai remarqué qu'écrire une nouvelle clause when écrase la précédente, mais ce n'est peut-être pas une façon propre de faire ? :/

          Merci encore pour ta réponse en tout cas et je serais ravi qu'on puisse discuter un peu plus pour que je puisse mieux comprendre tout ça !

          • Partager sur Facebook
          • Partager sur Twitter

          Problème de compréhension des mocks

          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
          • Editeur
          • Markdown