Mockito Comment se moquer uniquement de l'appel d'une méthode de la superclasse

94

J'utilise Mockito dans certains tests.

J'ai les classes suivantes:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        //some code  
        super.save();
    }  
}   

Je veux me moquer uniquement du deuxième appel ( super.save) de ChildService. Le premier appel doit appeler la méthode réelle. Y-a-t-il un moyen de faire ça?

mada
la source
Cela peut-il être résolu avec PowerMockito?
javaPlease42
@ javaPlease42: Oui, vous pouvez: stackoverflow.com/a/23884011/2049986 .
Jacob van Lingen

Réponses:

57

Non, Mockito ne supporte pas cela.

Ce n'est peut-être pas la réponse que vous recherchez, mais ce que vous voyez est le symptôme de la non-application du principe de conception:

Privilégier la composition à l'héritage

Si vous extrayez une stratégie au lieu d'étendre une super classe, le problème est résolu.

Si toutefois vous n'êtes pas autorisé à changer le code, mais que vous devez quand même le tester, et de cette manière maladroite, il y a encore de l'espoir. Avec certains outils AOP (par exemple AspectJ), vous pouvez intégrer du code dans la méthode de la super classe et éviter complètement son exécution (beurk). Cela ne fonctionne pas si vous utilisez des proxys, vous devez utiliser la modification de bytecode (soit le tissage au moment du chargement, soit le tissage au moment de la compilation). Il existe des frameworks moqueurs qui prennent également en charge ce type d'astuce, comme PowerMock et PowerMockito.

Je vous suggère d'opter pour la refactorisation, mais si ce n'est pas une option, vous vous amuserez sérieusement en matière de piratage.

iwein
la source
5
Je ne vois pas la violation de LSP. J'ai à peu près la même configuration que l'OP: une classe DAO de base avec une méthode findAll () et une sous-classe DAO qui remplace la méthode de base en appelant super.findAll () puis en triant le résultat. La sous-classe est substituable dans tous les contextes acceptant la superclasse. Suis-je mal compris votre signification?
1
Je vais supprimer la remarque LSP (cela n'ajoute pas de valeur à la réponse).
iwein
Oui, l'héritage est nul et le cadre stupide avec lequel je suis coincé est conçu avec l'héritage comme seule option.
Sridhar Sarnobat
En supposant que vous ne pouvez pas reconcevoir la superclasse, vous pouvez extraire le //some codescode dans une méthode qui pourrait être testée séparément.
Phasmal
1
Ok compris. C'est un problème différent de ce que j'essayais de résoudre lorsque j'ai cherché cela, mais au moins ce malentendu a résolu mon propre problème pour simuler un appel de la classe de base (que je ne remplace pas en effet).
Guillaume Perrot
86

Si vous n'avez vraiment pas le choix de refactoriser, vous pouvez tout simuler / stub dans l'appel de super méthode, par exemple

    class BaseService {

        public void validate(){
            fail(" I must not be called");
        }

        public void save(){
            //Save method of super will still be called.
            validate();
        }
    }

    class ChildService extends BaseService{

        public void load(){}

        public void save(){
            super.save();
            load();
        }
    }

    @Test
    public void testSave() {
        ChildService classToTest = Mockito.spy(new ChildService());

        // Prevent/stub logic in super.save()
        Mockito.doNothing().when((BaseService)classToTest).validate();

        // When
        classToTest.save();

        // Then
        verify(classToTest).load();
    }
jgonien
la source
2
ce code n'empêcherait pas le droit d'invocation de super.save (), donc si vous en faites beaucoup dans super.save (), vous devrez empêcher tous ces appels ...
iwein
Une solution fantastique fonctionne à merveille pour moi lorsque je veux renvoyer une valeur fictive d'une méthode de superclasse à l'usage de l'enfant, fantastique merci.
Gurnard
14
cela fonctionne bien à moins que la validation ne soit masquée ou que la méthode save fasse le travail directement au lieu d'appeler une autre méthode. mockito ne fait pas: Mockito.doNothing (). when ((BaseService) spy) .save (); cela ne fera rien sur la sauvegarde du service de base mais sur la sauvegarde de childService :(
tibi
Je ne peux pas faire fonctionner cela sur le mien - cela supprime aussi la méthode enfant. BaseServiceest abstrait, même si je ne vois pas pourquoi cela serait pertinent.
Sridhar Sarnobat
1
@ Sridhar-Sarnobat oui, je vois la même chose: (quelqu'un sait comment l'obtenir pour ne faire que sortir le super.validate()?
stantonk
4

Envisagez de refactoriser le code de la méthode ChildService.save () vers une méthode différente et testez cette nouvelle méthode au lieu de tester ChildService.save (), de cette façon vous éviterez les appels inutiles à la super méthode.

Exemple:

class BaseService {  
    public void save() {...}  
}

public Childservice extends BaseService {  
    public void save(){  
        newMethod();    
        super.save();
    }
    public void newMethod(){
       //some codes
    }
} 
Mohammed Misbahuddin
la source
1

créez une méthode package protected (suppose une classe de test dans le même package) dans la sous-classe qui appelle la méthode de super classe, puis appelez cette méthode dans votre méthode de sous-classe remplacée. vous pouvez ensuite définir des attentes sur cette méthode dans votre test grâce à l'utilisation du modèle d'espionnage. pas joli mais certainement mieux que de devoir gérer tous les paramètres d'attente pour la super méthode dans votre test

Luke
la source
puis-je aussi dire que la composition plutôt que l'héritage est presque toujours meilleure, mais parfois c'est simplement plus simple d'utiliser l'héritage. jusqu'à ce que java incorpore un meilleur modèle de composition, comme scala ou groovy, ce sera toujours le cas et ce problème continuera d'exister
Luke
1

Même si je suis totalement d'accord avec la réponse d'iwein (

privilégier la composition à l'héritage

), j'admets qu'il y a des moments où l'héritage semble tout simplement naturel, et je ne me sens pas le casser ou le refactoriser juste pour le plaisir d'un test unitaire.

Alors, ma suggestion:

/**
 * BaseService is now an asbtract class encapsulating 
 * some common logic callable by child implementations
 */
abstract class BaseService {  
    protected void commonSave() {
        // Put your common work here
    }

    abstract void save();
}

public ChildService extends BaseService {  
    public void save() {
        // Put your child specific work here
        // ...

        this.commonSave();
    }  
}

Et puis, dans le test unitaire:

    ChildService childSrv = Mockito.mock(ChildService.class, Mockito.CALLS_REAL_METHODS);

    Mockito.doAnswer(new Answer<Void>() {
        @Override
        public Boolean answer(InvocationOnMock invocation)
                throws Throwable {
            // Put your mocked behavior of BaseService.commonSave() here
            return null;
        }
    }).when(childSrv).commonSave();

    childSrv.save();

    Mockito.verify(childSrv, Mockito.times(1)).commonSave();

    // Put any other assertions to check child specific work is done
barrages50
la source
0

La raison en est que votre classe de base n'est pas publique, alors Mockito ne peut pas l'intercepter en raison de la visibilité, si vous changez la classe de base en public, ou @Override en sous-classe (en tant que public), alors Mockito peut se moquer correctement.

public class BaseService{
  public boolean foo(){
    return true;
  }
}

public ChildService extends BaseService{
}

@Test
@Mock ChildService childService;
public void testSave() {
  Mockito.when(childService.foo()).thenReturn(false);

  // When
  assertFalse(childService.foo());
}
zoufeiyy
la source
8
Ce n’est pas le problème. ChildService doit surcharger foo () et le problème est de savoir comment se moquer de BaseService.foo () mais pas de ChildService.foo ()
Adriaan Koster
0

Peut-être que l'option la plus simple si l'héritage a du sens est de créer une nouvelle méthode (package private ??) pour appeler le super (appelons-le superFindall), d'espionner l'instance réelle puis de se moquer de la méthode superFindAll () de la manière dont vous vouliez vous moquer la classe parent un. Ce n'est pas la solution parfaite en termes de couverture et de visibilité, mais elle devrait faire le travail et c'est facile à appliquer.

 public Childservice extends BaseService {
    public void save(){
        //some code
        superSave();
    }

    void superSave(){
        super.save();
    }
}
Rubasace
la source