Mockito: provare a spiare il metodo sta chiamando il metodo originale


350

Sto usando Mockito 1.9.0. Voglio prendere in giro il comportamento per un singolo metodo di una classe in un test JUnit, quindi ho

final MyClass myClassSpy = Mockito.spy(myInstance);
Mockito.when(myClassSpy.method1()).thenReturn(myResults);

Il problema è che nella seconda riga myClassSpy.method1()viene effettivamente chiamato, risultante in un'eccezione. L'unica ragione per cui sto usando i mock è che in seguito, ogni volta che myClassSpy.method1()viene chiamato, il metodo reale non verrà chiamato e l' myResultsoggetto verrà restituito.

MyClassè un'interfaccia ed myInstanceè un'implementazione di ciò, se ciò che conta.

Cosa devo fare per correggere questo comportamento di spionaggio?


Date un'occhiata a questo: stackoverflow.com/a/29394497/355438
Lu55

Risposte:


609

Vorrei citare la documentazione ufficiale :

Gotcha importante sullo spionaggio di oggetti reali!

A volte è impossibile usare quando (Oggetto) per stubbing spie. Esempio:

List list = new LinkedList();
List spy = spy(list);

// Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
when(spy.get(0)).thenReturn("foo");

// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);

Nel tuo caso va qualcosa del tipo:

doReturn(resulstIWant).when(myClassSpy).method1();

27
Cosa succede se utilizzo questo metodo e il mio originale viene chiamato STILL? Potrebbe esserci un problema con i parametri che passo? Ecco l'intero test: il metodo pastebin.com/ZieY790P send viene chiamato
Evgeni Petrov

26
@EvgeniPetrov se il tuo metodo originale viene ancora chiamato è probabilmente perché il tuo metodo originale è definitivo. Mockito non deride i metodi finali e non può avvisarti della derisione dei metodi finali.
MarcG,

1
è possibile anche per doThrow ()?
Gobliins,

1
sì, sfortunatamente i metodi statici sono smontabili e "non spia". Quello che faccio per gestire i metodi statici è avvolgere un metodo attorno alla chiamata statica e usare doNothing o doReturn su quel metodo. Con i singoli o gli oggetti scala muovo la carne della logica in una classe astratta e ciò mi dà la possibilità di avere un impl alternativo di classe test dell'oggetto su cui posso creare una spia.
Andrew Norman,

24
E se il metodo NOT final e NOT statico viene ancora chiamato?
X-HuMan,

27

Il mio caso era diverso dalla risposta accettata. Stavo cercando di deridere un metodo pacchetto-privato per un'istanza che non viveva in quel pacchetto

package common;

public class Animal {
  void packageProtected();
}

package instances;

class Dog extends Animal { }

e le classi di test

package common;

public abstract class AnimalTest<T extends Animal> {
  @Before
  setup(){
    doNothing().when(getInstance()).packageProtected();
  }

  abstract T getInstance();
}

package instances;

class DogTest extends AnimalTest<Dog> {
  Dog getInstance(){
    return spy(new Dog());
  }

  @Test
  public void myTest(){}
}

La compilazione è corretta, ma quando tenta di impostare il test, invoca invece il metodo reale.

Dichiarare il metodo protetto o pubblico risolve il problema, anche se non è una soluzione pulita.


2
Ho riscontrato un problema simile, ma il metodo test e package-private erano nello stesso pacchetto. Penso che forse Mockito abbia problemi con i metodi pacchetto-privati ​​in generale.
Dave,

22

Nel mio caso, usando Mockito 2.0, ho dovuto modificare tutti i any()parametri per bloccare nullable()la chiamata reale.


2
Non lasciare che 321 la migliore risposta votata ti abbassi, questo ha risolto il mio problema :) Ho lottato con questo per un paio d'ore!
Chris Kessel,

3
Questa è stata la risposta per me. Per rendere ancora più facile per coloro che seguono deridendo il tuo metodo, la sintassi è: foo = Mockito.spy(foo); Mockito.doReturn(someValue).when(foo).methodToPrevent(nullable(ArgumentType.class));
Stryder

Con Mockito 2.23.4 posso confermare che non è necessario, funziona bene con anye eqmatcher.
vmaldosan,

2
Ho provato tre diversi approcci sulla versione lib di 2.23.4: any (), eq () e nullable (). Solo gli ultimi hanno funzionato
Ryzhman l'

Ciao, la tua soluzione è davvero bella e ha funzionato anche per me. Grazie
Dhiren Solanki il

16

La risposta di Tomasz Nurkiewicz sembra non raccontare l'intera storia!

NB versione Mockito: 1.10.19.

Sono un principiante di Mockito, quindi non posso spiegare il seguente comportamento: se c'è un esperto là fuori che può migliorare questa risposta, non esitate.

Il metodo in questione qui, NONgetContentStringValue è e NON . final static

Questa linea fa chiamare il metodo originale getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), isA( ScoreDoc.class ));

Questa linea non chiama il metodo originale getContentStringValue:

doReturn( "dummy" ).when( im ).getContentStringValue( anyInt(), any( ScoreDoc.class ));

Per motivi ai quali non posso rispondere, l'utilizzo isA()causa il doReturnfallimento del comportamento previsto (?) "Non chiamare metodo" .

Diamo un'occhiata alle firme dei metodi coinvolti qui: sono entrambi staticmetodi di Matchers. Il Javadoc dice che entrambi ritornano null, il che è un po 'difficile da mettere in testa da soli. Presumibilmente l' Classoggetto passato mentre il parametro viene esaminato ma il risultato non viene mai calcolato o scartato. Dato che nullpuò rappresentare qualsiasi classe e che speri che il metodo deriso non venga chiamato, le firme di isA( ... )e any( ... )semplicemente restituiscono nullpiuttosto che un parametro generico * <T>?

Comunque:

public static <T> T isA(java.lang.Class<T> clazz)

public static <T> T any(java.lang.Class<T> clazz)

La documentazione dell'API non fornisce alcun indizio al riguardo. Sembra anche che la necessità di tale comportamento "non chiamare metodo" sia "molto rara". Personalmente io uso questa tecnica per tutto il tempo : in genere trovo che beffarda coinvolge poche righe che "fissano la scena" ... seguito chiamando un metodo che poi "gioca" la scena nel contesto finta che avete messo in scena .. e mentre stai allestendo lo scenario e gli oggetti di scena, l'ultima cosa che vuoi è che gli attori entrino sul palco a sinistra e inizino a recitare il loro cuore ...

Ma questo va ben oltre il mio voto ... Invito spiegazioni da qualsiasi sommo sacerdote Mockito di passaggio ...

* è "parametro generico" il termine giusto?


Non so se questo aggiunge chiarezza o confonde ulteriormente la questione, ma la differenza tra isA () e any () è che isA in realtà fa il controllo del tipo, mentre qualsiasi famiglia di metodi () è stata creata semplicemente per evitare il cast del tipo di discussione.
Kevin Welker,

@KevinWelker Grazie. E in effetti i nomi dei metodi non mancano di una certa qualità autoesplicativa. Comunque, e per quanto debolmente, metto in discussione il geniale designer Mockito per non aver documentato adeguatamente. Senza dubbio ho bisogno di leggere l'ennesimo libro su Mockito. PS in realtà sembra che ci siano pochissime risorse per insegnare "Mockito intermedio"!
mike rodent,

1
La storia è che i metodi anyXX sono stati creati prima come un modo per gestire solo la tipografia. Quindi, quando è stato suggerito di aggiungere la verifica dell'argomento, non volevano interrompere gli utenti dell'API esistente, quindi hanno creato la famiglia isA (). Sapendo che i metodi any () avrebbero dovuto effettuare il controllo del tipo da sempre, hanno ritardato la modifica di quelli fino a quando non hanno introdotto altri cambiamenti di rottura nella revisione di Mockito 2.X (che non ho ancora provato). In 2.x +, i metodi anyX () sono alias per i metodi isA ().
Kevin Welker,

Grazie. Questa è una risposta fondamentale per quelli di noi che fanno diversi aggiornamenti della libreria allo stesso tempo, perché il codice che era in esecuzione non funzionava improvvisamente e silenziosamente.
Dex Stakker,

6

Un altro possibile scenario che può causare problemi con le spie è quando testate i bean di primavera (con il framework dei test di primavera) o qualche altro framework che esegue il proxy degli oggetti durante il test .

Esempio

@Autowired
private MonitoringDocumentsRepository repository

void test(){
    repository = Mockito.spy(repository)
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

Nel codice sopra sia Spring che Mockito tenteranno di eseguire il proxy dell'oggetto MonitoringDocumentsRepository, ma Spring sarà la prima, il che provocherà una vera chiamata al metodo findMonitoringDocuments. Se eseguiamo il debug del nostro codice subito dopo aver inserito una spia nell'oggetto repository, apparirà così all'interno del debugger:

repository = MonitoringDocumentsRepository$$EnhancerBySpringCGLIB$$MockitoMock$

@SpyBean in soccorso

Se invece l' @Autowiredannotazione utilizziamo l' @SpyBeanannotazione, risolveremo il problema sopra riportato, l'annotazione SpyBean inietterà anche l'oggetto repository ma sarà inizialmente proxy da Mockito e apparirà così all'interno del debugger

repository = MonitoringDocumentsRepository$$MockitoMock$$EnhancerBySpringCGLIB$

ed ecco il codice:

@SpyBean
private MonitoringDocumentsRepository repository

void test(){
    Mockito.doReturn(docs1, docs2)
            .when(repository).findMonitoringDocuments(Mockito.nullable(MonitoringDocumentSearchRequest.class));
}

1

Ho trovato un altro motivo per cui la spia chiama il metodo originale.

Qualcuno ha avuto l'idea di deridere una finallezione e ha scoperto MockMaker:

Dato che funziona diversamente dal nostro attuale meccanismo e questo ha diversi limiti e poiché vogliamo raccogliere esperienza e feedback degli utenti, questa funzione ha dovuto essere esplicitamente attivata per essere disponibile; può essere fatto tramite il meccanismo di estensione mockito creando il file src/test/resources/mockito-extensions/org.mockito.plugins.MockMakercontenente una singola riga:mock-maker-inline

Fonte: https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#mock-the-unmockable-opt-in-mocking-of-final-classesmethods

Dopo aver unito e portato quel file sul mio computer, i miei test fallirono.

Ho dovuto solo rimuovere la riga (o il file) e ho spy()funzionato.


questa era la ragione nel mio caso, stavo cercando di deridere un metodo finale, ma continuava a chiamare quello reale senza un chiaro messaggio di errore che era confuso.
Bashar Ali Labadi,

1

Un po 'tardi alla festa, ma le soluzioni di cui sopra non hanno funzionato per me, quindi condividendo i miei 0,02 $

Versione Mokcito: 1.10.19

MyClass.java

private int handleAction(List<String> argList, String action)

Test.java

MyClass spy = PowerMockito.spy(new MyClass());

Di seguito NON ha funzionato per me (il metodo effettivo è stato chiamato):

1.

doReturn(0).when(spy , "handleAction", ListUtils.EMPTY_LIST, new String());

2.

doReturn(0).when(spy , "handleAction", any(), anyString());

3.

doReturn(0).when(spy , "handleAction", null, null);

A seguito LAVORATO:

doReturn(0).when(spy , "handleAction", any(List.class), anyString());

0

Un modo per assicurarsi che non venga chiamato un metodo da una classe è quello di sovrascrivere il metodo con un manichino.

    WebFormCreatorActivity activity = spy(new WebFormCreatorActivity(clientFactory) {//spy(new WebFormCreatorActivity(clientFactory));
            @Override
            public void select(TreeItem i) {
                log.debug("SELECT");
            };
        });

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.