web-dev-qa-db-fra.com

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

J'utilise Mockito dans certains tests.

J'ai les cours suivants: 

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

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

Je veux me moquer que le 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?

78
mada

Non, Mockito ne supporte pas cela.

Ce n'est peut-être pas la réponse que vous cherchez, mais ce que vous voyez est un symptôme de ne pas appliquer le 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 disparaît.

Si toutefois vous n'êtes pas autorisé à modifier le code, mais que vous devez quand même le tester, et de cette manière maladroite, il y a toujours de l'espoir. Avec certains outils AOP (par exemple AspectJ), vous pouvez incorporer du code dans la méthode super class et éviter entièrement son exécution (beurk). Cela ne fonctionne pas si vous utilisez des proxys, vous devez utiliser la modification de code-octet (tissage au temps de chargement ou tissage au temps de compilation). Il existe également des frameworks moqueurs prenant en charge ce type d’astuce, tels que PowerMock et PowerMockito.

Je vous suggère de vous lancer dans la refactorisation, mais si ce n’est pas une option envisageable, vous vous amuserez sérieusement.

54
iwein

Si vous n'avez vraiment pas le choix de refactoriser, vous pouvez simuler/stub tout ce qui se trouve dans l'appel de la 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();
    }
73
jgonian

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 (). Ainsi, vous éviterez un appel inutile à 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
    }
} 
4

créez une méthode de package protected (suppose la classe de test dans le même package) dans la sous-classe qui appelle la méthode super class, puis appelez cette méthode dans la méthode de sous-classe remplacée vous pouvez ensuite définir les attentes sur cette méthode dans votre test en utilisant le motif espion. pas joli mais certainement mieux que d'avoir à gérer toutes les attentes de la super méthode de votre test

1
Luke

Si l’héritage a du sens, l’option la plus simple est peut-être 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 même manière la classe mère un. Ce n'est pas la solution parfaite en termes de couverture et de visibilité, mais il convient de faire le travail et il est facile à appliquer. 

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

    void superSave(){
        super.save();
    }
}
0
Rubasace

Même si je suis tout à fait d’accord avec la réponse de iwein (

favoriser la composition par rapport à l'héritage

), j’admets qu’il y a des cas où l’héritage semble naturel, et je ne me sens pas brisé ou refactorisé par un test unitaire.

Donc, 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
0
dams50

La raison en est que votre classe de base n'est pas publique, alors Mockito ne peut pas l'intercepter pour des raisons de visibilité. Si vous changez la classe de base en public ou @Override dans la sous-classe (en tant que public), Mockito peut s'en 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());
}
0
zoufeiyy