Mockito Come deridere solo il richiamo di un metodo della superclasse


94

Sto usando Mockito in alcuni test.

Ho le seguenti classi:

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

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

Voglio prendere in giro solo la seconda chiamata ( super.save) di ChildService. La prima chiamata deve chiamare il metodo reale. C'è un modo per farlo?


Può essere risolto con PowerMockito?
java 42

Risposte:


57

No, Mockito non lo supporta.

Questa potrebbe non essere la risposta che stai cercando, ma quello che vedi è un sintomo della mancata applicazione del principio di progettazione:

Preferisci la composizione all'eredità

Se estrai una strategia invece di estendere una super classe il problema è sparito.

Se invece non ti è permesso modificare il codice, ma devi comunque provarlo, e in questo modo scomodo, c'è ancora speranza. Con alcuni strumenti AOP (ad esempio AspectJ) puoi intrecciare il codice nel metodo della super classe ed evitarne completamente l'esecuzione (bleah). Questo non funziona se stai usando i proxy, devi usare la modifica del bytecode (o la tessitura del tempo di caricamento o la tessitura del tempo di compilazione). Esistono anche framework beffardi che supportano questo tipo di trucco, come PowerMock e PowerMockito.

Ti suggerisco di fare il refactoring, ma se questa non è un'opzione sei pronto per un serio divertimento di hacking.


5
Non vedo la violazione LSP. Ho all'incirca la stessa configurazione dell'OP: una classe DAO di base con un metodo findAll () e una sottoclasse DAO che sovrascrive il metodo di base chiamando super.findAll () e quindi ordinando il risultato. La sottoclasse è sostituibile in tutti i contesti che accettano la superclasse. Sto fraintendendo il tuo significato?

1
Rimuoverò l'osservazione LSP (non aggiunge valore alla risposta).
iwein

Sì, l'ereditarietà fa schifo e lo stupido framework con cui sono bloccato è progettato con l'ereditarietà come unica opzione.
Sridhar Sarnobat

Supponendo che non sia possibile riprogettare la superclasse, è possibile estrarre il //some codescodice in un metodo che potrebbe essere testato separatamente.
Fasale

1
Ok capito. È un problema diverso da quello che stavo cercando di risolvere quando l'ho cercato, ma almeno quell'incomprensione ha risolto il mio problema per simulare la chiamata dalla classe base (che non sovrascrivo effettivamente).
Guillaume Perrot,

86

Se davvero non hai una scelta per il refactoring, puoi deridere / stub tutto nella chiamata al super metodo es

    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();
    }

2
questo codice non impedirebbe effettivamente l'invocazione super.save (), quindi se fai molto in super.save () dovresti impedire tutte quelle chiamate ...
iwein

la soluzione fantastica fa miracoli per me quando voglio restituire un valore deriso da un metodo superclasse per l'uso da parte del bambino, fantastico grazie.
Gurnard

14
funziona bene a meno che la convalida non sia nascosta o il metodo save faccia il lavoro direttamente invece di chiamare un altro metodo. mockito non: Mockito.doNothing (). when ((BaseService) spy) .save (); questo non farà nulla sul salvataggio del servizio di base ma sul salvataggio del servizio bambino :(
tibi

Non riesco a farlo funzionare sul mio - blocca anche il metodo del bambino. BaseServiceè astratto, anche se non vedo perché sarebbe rilevante.
Sridhar Sarnobat

1
@ Sridhar-Sarnobat sì, vedo la stessa cosa :( qualcuno sa come farlo spegnere solo il super.validate()?
stantonk

4

Prendi in considerazione il refactoring del codice dal metodo ChildService.save () a un metodo diverso e prova quel nuovo metodo invece di testare ChildService.save (), in questo modo eviterai chiamate non necessarie al super metodo.

Esempio:

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

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

1

creare un metodo protetto dal pacchetto (presuppone la classe di test nello stesso pacchetto) nella sottoclasse che chiama il metodo della super classe e quindi chiamare quel metodo nel metodo della sottoclasse sovrascritto. puoi quindi impostare le aspettative su questo metodo nel tuo test attraverso l'uso del pattern spia. non carino ma sicuramente meglio che dover affrontare tutte le aspettative per il super metodo nel tuo test


posso anche dire che la composizione rispetto all'ereditarietà è quasi sempre migliore, ma a volte è semplicemente più semplice usare l'ereditarietà. finché java non incorporerà un modello compositivo migliore, come scala o groovy, questo sarà sempre il caso e questo problema continuerà ad esistere
Luca

1

Anche se sono totalmente d'accordo con la risposta di iwein (

privilegiare la composizione rispetto all'eredità

), ammetto che a volte l'ereditarietà sembra naturale, e non mi sento di romperla o rifattorizzarla solo per il bene di uno unit test.

Quindi, il mio suggerimento:

/**
 * 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();
    }  
}

E poi, nello unit test:

    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

Il motivo è che la tua classe base non è pubblica, quindi Mockito non può intercettarla a causa della visibilità, se cambi la classe base come pubblica, o @Override nella sottoclasse (come pubblica), Mockito può prenderla in giro correttamente.

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());
}

8
Questo non è il punto. ChildService dovrebbe sovrascrivere foo () e il problema è come deridere BaseService.foo () ma non ChildService.foo ()
Adriaan Koster

0

Forse l'opzione più semplice se l'ereditarietà ha senso è creare un nuovo metodo (pacchetto privato ??) per chiamare il super (chiamiamolo superFindall), spiare l'istanza reale e quindi deridere il metodo superFindAll () nel modo in cui volevi deridere quello della classe genitore. Non è la soluzione perfetta in termini di copertura e visibilità, ma dovrebbe fare il lavoro ed è facile da applicare.

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

    void superSave(){
        super.save();
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.