Test de la méthode privée à l'aide de mockito

104
classe publique A {

    méthode publique void (booléen b) {
          si (b == vrai)
               method1 ();
          autre
               method2 ();
    }

    private void method1 () {}
    private void method2 () {}
}
public class TestA {

    @Tester
    public void testMethod () {
      A a = simulé (classe A.);
      a.method (vrai);
      // comment tester comme verify (a) .method1 ();
    }
}

Comment tester la méthode privée est appelée ou non, et comment tester la méthode privée en utilisant mockito ???

Nageswaran
la source
En dépit d'être spécifique à mockito , c'est essentiellement la même question que Comment tester une fonction privée ou une classe qui a des méthodes privées, des champs ou des classes internes?
Raedwald

Réponses:

81

Vous ne pouvez pas faire cela avec Mockito mais vous pouvez utiliser Powermock pour étendre Mockito et simuler des méthodes privées. Powermock soutient Mockito. Voici un exemple.

shift66
la source
19
Je suis confus avec cette réponse. C'est moqueur, mais le titre teste les méthodes privées
diyoda_
J'ai utilisé Powermock pour se moquer de la méthode privée, mais comment puis-je tester la méthode privée avec Powermock. Où, je peux passer une entrée et attendre une sortie de la méthode, puis vérifier la sortie?
Rito
Vous ne pouvez pas. Vous vous moquez de la sortie d'entrée, vous ne pouvez pas tester la fonctionnalité réelle.
Talha
131

Pas possible via mockito. De leur wiki

Pourquoi Mockito ne se moque pas des méthodes privées?

Premièrement, nous ne sommes pas dogmatiques en nous moquant des méthodes privées. Nous ne nous soucions tout simplement pas des méthodes privées, car du point de vue des tests, les méthodes privées n'existent pas. Voici quelques raisons pour lesquelles Mockito ne se moque pas des méthodes privées:

Cela nécessite le piratage des chargeurs de classe qui ne sont jamais à l'épreuve des balles et cela change l'API (vous devez utiliser un testeur personnalisé, annoter la classe, etc.).

Il est très facile de contourner le problème - il suffit de changer la visibilité de la méthode de privée à package-protected (ou protected).

Cela me demande de passer du temps à le mettre en œuvre et à le maintenir. Et cela n'a pas de sens étant donné le point n ° 2 et le fait qu'il est déjà implémenté dans un outil différent (powermock).

Enfin ... Se moquer des méthodes privées est un indice qu'il y a quelque chose qui ne va pas avec la compréhension OO. Dans OO, vous voulez que les objets (ou rôles) collaborent, pas les méthodes. Oubliez pascal et le code procédural. Pensez aux objets.

Aravind Yarram
la source
1
Il y a une hypothèse fatale faite par cette déclaration:> Se moquer des méthodes privées est un indice qu'il y a quelque chose qui ne va pas avec la compréhension OO. Si je teste une méthode publique et qu'elle appelle des méthodes privées, je voudrais me moquer des retours de méthode privée. Suivre l'hypothèse ci-dessus évite d'avoir à implémenter même des méthodes privées. Comment est-ce une mauvaise compréhension de OO?
oeufs le
1
@eggmatters Selon Baeldung "Les techniques de moquerie devraient être appliquées aux dépendances externes de la classe et non à la classe elle-même. Si la moquerie des méthodes privées est essentielle pour tester nos classes, cela indique généralement une mauvaise conception." Voici un fil de discussion sympa à ce sujet softwareengineering.stackexchange.com/questions/100959/…
Jason Glez
34

Voici un petit exemple comment le faire avec powermock

public class Hello {
    private Hello obj;
    private Integer method1(Long id) {
        return id + 10;
    }
} 

Pour tester la méthode 1, utilisez le code:

Hello testObj = new Hello();
Integer result = Whitebox.invokeMethod(testObj, "method1", new Long(10L));

Pour définir un objet privé obj, utilisez ceci:

Hello testObj = new Hello();
Hello newObject = new Hello();
Whitebox.setInternalState(testObj, "obj", newObject);
Mindaugas Jaraminas
la source
Votre lien pointe uniquement vers le power mock repo @Mindaugas
Xavier
@Xavier vrai. Vous pouvez l'utiliser si vous le souhaitez dans votre projet.
Mindaugas Jaraminas
1
Formidable !!! si bien expliqué avec ces exemples simples presque tout :) Parce que le but est juste de tester le code et non ce que tout le framework fournit :)
siddhusingh
Veuillez mettre à jour ceci. Whitebox ne fait plus partie de l'API publique.
user447607
17

Pensez-y en termes de comportement, pas en termes de méthodes existantes. La méthode appelée methoda un comportement particulier si best vrai. Il a un comportement différent s'il best faux. Cela signifie que vous devez écrire deux tests différents pour method; un pour chaque cas. Donc, au lieu d'avoir trois tests orientés méthode (un pour method, un pour method1, un pour method2, vous avez deux tests orientés comportement.

Lié à ceci (j'ai suggéré ceci dans un autre fil SO récemment, et j'ai été appelé un mot de quatre lettres en conséquence, alors n'hésitez pas à prendre ceci avec un grain de sel); Je trouve utile de choisir des noms de test qui reflètent le comportement que je teste, plutôt que le nom de la méthode. Donc , ne pas appeler vos tests testMethod(), testMethod1(), testMethod2()et ainsi de suite. J'aime les noms comme calculatedPriceIsBasePricePlusTax()ou taxIsExcludedWhenExcludeIsTrue()qui indiquent quel comportement je teste; puis dans chaque méthode d'essai, testez uniquement le comportement indiqué. La plupart de ces comportements impliqueront un seul appel à une méthode publique, mais peuvent impliquer de nombreux appels à des méthodes privées.

J'espère que cela t'aides.

Dawood ibn Kareem
la source
13

Bien que Mockito ne fournisse pas cette fonctionnalité, vous pouvez obtenir le même résultat en utilisant Mockito + la classe JUnit ReflectionUtils ou la classe Spring ReflectionTestUtils . Veuillez voir un exemple ci-dessous tiré d' ici expliquant comment invoquer une méthode privée:

ReflectionTestUtils.invokeMethod(student, "saveOrUpdate", "From Unit test");

Des exemples complets avec ReflectionTestUtils et Mockito peuvent être trouvés dans le livre Mockito for Spring

AR1
la source
ReflectionTestUtils.invokeMethod (étudiant, "saveOrUpdate", "argument1", "argument2", "argument3"); Le dernier argument de invokeMethod utilise Vargs qui peut prendre plusieurs arguments qui doivent être passés à la méthode privée. Ça marche.
Tim
Cette réponse devrait avoir plus de votes positifs, de loin le moyen le plus simple de tester des méthodes privées.
Max
9

Vous n'êtes pas censé tester des méthodes privées. Seules les méthodes non privées doivent être testées car elles doivent de toute façon appeler les méthodes privées. Si vous «souhaitez» tester des méthodes privées, cela peut indiquer que vous devez repenser votre conception:

Est-ce que j'utilise une injection de dépendance appropriée? Dois-je éventuellement déplacer les méthodes privées dans une classe séparée et plutôt tester cela? Ces méthodes doivent-elles être privées? ... ne peuvent-ils pas être par défaut ou protégés plutôt?

Dans l'exemple ci-dessus, les deux méthodes appelées "au hasard" peuvent en fait devoir être placées dans une classe à part, testées puis injectées dans la classe ci-dessus.

Jaco Van Niekerk
la source
26
Un point valable. Cependant, n'est-ce pas la raison pour laquelle l'utilisation d'un modificateur privé pour les méthodes est parce que vous voulez simplement couper les codes trop longs et / ou répétitifs? Le séparer en une autre classe, c'est comme si vous faisiez la promotion de ces lignes de codes comme des citoyens de première classe qui ne seront réutilisés nulle part ailleurs, car il était spécifiquement destiné à partitionner les codes longs et à empêcher la répétition de lignes de codes. Si vous allez le séparer dans une autre classe, cela ne semble pas juste; vous obtiendriez facilement une explosion de classe.
supertonsky
2
Noté supertonsky, je faisais référence au cas général. Je conviens que dans le cas ci-dessus, il ne devrait pas être dans une classe séparée. (+1 sur votre commentaire cependant - c'est un point très valable que vous faites sur la promotion des membres privés)
Jaco Van Niekerk
4
@supertonsky, je n'ai pas pu trouver de réponse satisfaisante à ce problème. Il y a plusieurs raisons pour lesquelles je pourrais utiliser des membres privés et très souvent ils n'indiquent pas une odeur de code et je gagnerais grandement à les tester. Les gens semblent balayer cela en disant «ne le faites pas».
LuddyPants
3
Désolé, j'ai opté pour un vote négatif basé sur "Si vous 'voulez' tester des méthodes privées, cela peut indiquer que vous devez voter contre votre conception". OK, c'est juste, mais l'une des raisons de tester est que, dans un délai où vous n'avez pas le temps de repenser la conception, vous essayez d'implémenter en toute sécurité un changement qui doit être apporté à un méthode privée. Dans un monde idéal, cette méthode privée n'aurait-elle pas besoin d'être modifiée parce que la conception serait parfaite? Bien sûr, mais dans un monde parfait, mais c'est sans objet car dans un monde parfait qui a besoin de tests, tout fonctionne. :)
John Lockwood
2
@John. Point pris, votre vote défavorable justifié (+1). Merci pour le commentaire également - je suis d'accord avec vous sur l'argument que vous faites. Dans de tels cas, je peux voir l'une des deux options suivantes: Soit la méthode est faite package-private ou protected et les tests unitaires sont écrits comme d'habitude; ou (et c'est ironique et mauvaise pratique) une méthode principale est rapidement écrite pour s'assurer qu'elle fonctionne toujours. Cependant, ma réponse était basée sur un scénario d'écriture de NOUVEAU code et non de refactorisation lorsque vous ne pourrez peut-être pas altérer la conception d'origine.
Jaco Van Niekerk
6

J'ai pu tester une méthode privée à l'intérieur en utilisant mockito en utilisant la réflexion. Voici l'exemple, j'ai essayé de le nommer de manière à ce qu'il ait du sens

//Service containing the mock method is injected with mockObjects

@InjectMocks
private ServiceContainingPrivateMethod serviceContainingPrivateMethod;

//Using reflection to change accessibility of the private method

Class<?>[] params = new Class<?>[]{PrivateMethodParameterOne.class, PrivateMethodParameterTwo.class};
    Method m = serviceContainingPrivateMethod .getClass().getDeclaredMethod("privateMethod", params);
    //making private method accessible
    m.setAccessible(true); 
    assertNotNull(m.invoke(serviceContainingPrivateMethod, privateMethodParameterOne, privateMethodParameterTwo).equals(null));
Abdullah Choudhury
la source
6
  1. En utilisant la réflexion, les méthodes privées peuvent être appelées à partir de classes de test. Dans ce cas,

    // la méthode de test sera comme ça ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        // invoke the private method for test
        privateMethod.invoke(A, null);
    
        }
    }
  2. Si la méthode privée appelle une autre méthode privée, nous devons espionner l'objet et stuber l'autre méthode.La classe de test sera comme ...

    // la méthode de test sera comme ça ...

    public class TestA {
    
      @Test
        public void testMethod() {
    
        A a= new A();
        A spyA = spy(a);
        Method privateMethod = A.class.getDeclaredMethod("method1", null);
        privateMethod.setAccessible(true);
        doReturn("Test").when(spyA, "method2"); // if private method2 is returning string data
        // invoke the private method for test
        privateMethod.invoke(spyA , null);
    
        }
    }

** L'approche consiste à combiner réflexion et espionnage de l'objet. ** method1 et ** method2 sont des méthodes privées et method1 appelle method2.

Singh Arun
la source
4

Je ne comprends pas vraiment votre besoin de tester la méthode privée. Le problème racine est que votre méthode publique a void comme type de retour et que vous ne pouvez donc pas tester votre méthode publique. Par conséquent, vous êtes obligé de tester votre méthode privée. Ma supposition est-elle correcte?

Quelques solutions possibles (AFAIK):

  1. Vous vous moquez de vos méthodes privées, mais vous ne testerez pas "réellement" vos méthodes.

  2. Vérifiez l'état de l'objet utilisé dans la méthode. La plupart des méthodes effectuent un certain traitement des valeurs d'entrée et renvoient une sortie, ou modifient l'état des objets. Le test des objets pour l'état souhaité peut également être utilisé.

    public class A{
    
    SomeClass classObj = null;
    
    public void publicMethod(){
       privateMethod();
    }
    
    private void privateMethod(){
         classObj = new SomeClass();
    }
    
    }

    [Ici, vous pouvez tester la méthode privée, en vérifiant le changement d'état du classObj de null à non null.]

  3. Refactorisez un peu votre code (j'espère que ce n'est pas un ancien code). Mon principe d'écriture d'une méthode est que, il faut toujours retourner quelque chose (un int / un booléen). La valeur retournée PEUT ou PEUT NE PAS être utilisée par l'implémentation, mais elle sera VRAIMENT utilisée par le test

    code.

    public class A
    { 
        public int method(boolean b)
        {
              int nReturn = 0;
              if (b == true)
                   nReturn = method1();
              else
                   nReturn = method2();
        }
    
        private int method1() {}
    
        private int method2() {}
    
    }
Reji
la source
3

Il existe en fait un moyen de tester les méthodes d'un membre privé avec Mockito. Disons que vous avez une classe comme celle-ci:

public class A {
    private SomeOtherClass someOtherClass;
    A() {
        someOtherClass = new SomeOtherClass();
    }
    public void method(boolean b){
        if (b == true)
            someOtherClass.method1();
        else
            someOtherClass.method2();
    }

}

public class SomeOtherClass {
    public void method1() {}
    public void method2() {}
}

Si vous voulez tester a.method, vous invoquerez une méthode à partir de SomeOtherClass, vous pouvez écrire quelque chose comme ci-dessous.

@Test
public void testPrivateMemberMethodCalled() {
    A a = new A();
    SomeOtherClass someOtherClass = Mockito.spy(new SomeOtherClass());
    ReflectionTestUtils.setField( a, "someOtherClass", someOtherClass);
    a.method( true );

    Mockito.verify( someOtherClass, Mockito.times( 1 ) ).method1();
}

ReflectionTestUtils.setField(); va stuber le membre privé avec quelque chose que vous pouvez espionner.

Fan Jin
la source
2

Mettez votre test dans le même package, mais dans un dossier source différent (src / main / java vs src / test / java) et rendez ces méthodes package-private. La testabilité IMO est plus importante que la confidentialité.

Roland Schneider
la source
6
La seule raison légitime est de tester une partie d'un système hérité. Si vous commencez à tester la méthode private / package-private, vous exposez vos objets internes. Cela entraîne généralement un mauvais code refactorable. Préférez la composition afin que vous puissiez obtenir la testabilité, avec toute la bonté d'un système orienté objet.
Brice
1
D'accord - ce serait la manière préférée. Cependant, si vous voulez vraiment tester des méthodes privées avec mockito, c'est la seule option (typeafe) que vous avez. Ma réponse était un peu hâtive cependant, j'aurais dû souligner les risques, comme vous et les autres l'avez fait.
Roland Schneider
C'est ma manière préférée. Il n'y a rien de mal à exposer un objet interne au niveau package-private; et le test unitaire est un test de boîte blanche, vous devez connaître l'interne pour le test.
Andrew Feng
0

Dans les cas où la méthode privée n'est pas void et que la valeur de retour est utilisée comme paramètre de la méthode d'une dépendance externe, vous pouvez simuler la dépendance et utiliser un ArgumentCaptorpour capturer la valeur de retour. Par exemple:

ArgumentCaptor<ByteArrayOutputStream> csvOutputCaptor = ArgumentCaptor.forClass(ByteArrayOutputStream.class);
//Do your thing..
verify(this.awsService).uploadFile(csvOutputCaptor.capture());
....
assertEquals(csvOutputCaptor.getValue().toString(), "blabla");
laiteux
la source