Qual è la differenza tra deridere e spiare quando si utilizza Mockito?


137

Quale sarebbe un caso d'uso per l'uso di una spia Mockito?

Mi sembra che ogni caso di utilizzo della spia possa essere gestito con una simulazione, usando callRealMethod.

Una differenza che posso vedere è che se vuoi che la maggior parte delle chiamate al metodo siano reali, salva alcune righe di codice per usare una simulazione o una spia. È quello o mi sto perdendo il quadro più ampio?

Risposte:


100

La risposta è nella documentazione :

Derisioni parziali reali (da 1.8.0)

Alla fine, dopo molti dibattiti interni e discussioni sulla mailing list, è stato aggiunto un supporto finto parziale a Mockito. In precedenza abbiamo considerato le derisioni parziali come odori di codice. Tuttavia, abbiamo trovato un caso d'uso legittimo per derisioni parziali.

Prima della versione 1.8 spy () non produceva simulazioni parziali reali ed era confuso per alcuni utenti. Maggiori informazioni sullo spionaggio: qui o in javadoc per il metodo spy (Object).

callRealMethod()è stato introdotto dopo spy(), ma spy () è stato lasciato lì ovviamente, per garantire la retrocompatibilità.

Altrimenti, hai ragione: tutti i metodi di una spia sono reali a meno che non vengano messi a tacere. Tutti i metodi di un finto vengono eliminati a meno che non callRealMethod()venga chiamato. In generale, preferirei usare callRealMethod(), perché non mi costringe a usare il doXxx().when()linguaggio al posto del tradizionalewhen().thenXxx()


In questi casi il problema con la preferenza di deridere sulla spia è quando la classe usa un membro che non viene iniettato (ma inizializzato localmente) e che viene successivamente utilizzato dal metodo "reale"; nella simulazione, il membro verrà inizializzato al suo valore Java predefinito, che potrebbe causare comportamenti errati o persino una NullPointerException. Il modo per passare questo è aggiungere un metodo "init" e poi "davvero" chiamarlo, ma questo mi sembra un po 'esagerato.
Eyal Roth,

Dal documento: "le spie dovrebbero essere usate con attenzione e occasionalmente, ad esempio quando si tratta di codice legacy". Lo spazio di test dell'unità soffre di troppi modi per fare la stessa cosa.
gdbj,

89

Differenza tra una spia e un finto

Quando Mockito crea una simulazione, lo fa dalla Classe di un Tipo, non da un'istanza reale. La simulazione crea semplicemente un'istanza di shell bare-class della Classe, interamente strumentata per tracciare le interazioni con essa. D'altra parte, la spia avvolgerà un'istanza esistente. Si comporterà comunque allo stesso modo dell'istanza normale: l'unica differenza è che sarà anche strumentato per tracciare tutte le interazioni con esso.

Nel seguente esempio, creiamo un mock della classe ArrayList:

@Test
public void whenCreateMock_thenCreated() {
    List mockedList = Mockito.mock(ArrayList.class);

    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

    assertEquals(0, mockedList.size());
}

Come puoi vedere - l'aggiunta di un elemento nell'elenco deriso in realtà non aggiunge nulla - chiama semplicemente il metodo senza altri effetti collaterali. D'altra parte, una spia si comporterà diversamente: in realtà chiamerà la vera implementazione del metodo add e aggiungerà l'elemento alla lista sottostante:

@Test
public void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());
    spyList.add("one");
    Mockito.verify(spyList).add("one");

    assertEquals(1, spyList.size());
}

Qui possiamo sicuramente dire che il vero metodo interno dell'oggetto è stato chiamato perché quando si chiama il metodo size () si ottiene la dimensione come 1, ma questo metodo size () non è stato deriso! Quindi da dove viene? Il metodo size () reale interno è chiamato come size () non è deriso (o stub) e quindi possiamo dire che la voce è stata aggiunta all'oggetto reale.

Fonte: http://www.baeldung.com/mockito-spy + note personali.


1
Non intendi size () restituisce 1?
nero,

Nel primo esempio, perché viene mockedList.size()restituito 0se neanche questo metodo è stato cancellato? È solo un valore predefinito dato il tipo restituito del metodo?
mike,

@mike: mockedList.size()restituisce un intvalore predefinito di int0 in Java. Se si tenta di eseguire assertEquals(0, mockedList.size());dopo mockedList.clear();, il risultato rimane lo stesso.
realPK

2
Questa risposta è ben scritta e semplice e mi ha aiutato a capire finalmente la differenza tra finto e spia. Ben fatto.
Pesa

38

Se esiste un oggetto con 8 metodi e hai un test in cui vuoi chiamare 7 metodi reali e stub un metodo hai due opzioni:

  1. Usando un mock dovresti impostarlo invocando 7 callRealMethod e stub un metodo
  2. Usando un spydevi impostarlo stubando un metodo

La documentazione ufficiale su doCallRealMethodraccomanda di usare una spia per derisioni parziali.

Vedi anche javadoc spy (Object) per saperne di più sulle simulazioni parziali. Mockito.spy () è un modo consigliato di creare simulazioni parziali. Il motivo è che garantisce che i metodi reali vengano chiamati contro l'oggetto correttamente costruito perché sei responsabile della costruzione dell'oggetto passato al metodo spy ().


5

La spia può essere utile quando si desidera creare unit test per il codice legacy .

Ho creato un esempio eseguibile qui https://www.surasint.com/mockito-with-spy/ , ne copio alcuni qui.

Se hai qualcosa come questo codice:

public void transfer(  DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService, 
             double amount, String fromAccount, String toAccount){
    withdrawMoneyService.withdraw(fromAccount,amount);
    depositMoneyService.deposit(toAccount,amount);
}

Potresti non aver bisogno di spia perché puoi semplicemente deridere DepositMoneyService e WithdrawMoneyService.

Ma con alcuni codici legacy, la dipendenza è nel codice in questo modo:

    public void transfer(String fromAccount, String toAccount, double amount){

        this.depositeMoneyService = new DepositMoneyService();
        this.withdrawMoneyService = new WithdrawMoneyService();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

Sì, è possibile passare al primo codice ma poi l'API viene modificata. Se questo metodo viene utilizzato da molti luoghi, è necessario modificarli tutti.

L'alternativa è che puoi estrarre la dipendenza in questo modo:

    public void transfer(String fromAccount, String toAccount, double amount){
        this.depositeMoneyService = proxyDepositMoneyServiceCreator();
        this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }
    DepositMoneyService proxyDepositMoneyServiceCreator() {
        return new DepositMoneyService();
    }

    WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
        return new WithdrawMoneyService();
    }

Quindi puoi usare la spia per iniettare la dipendenza in questo modo:

DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
        WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);

    TransferMoneyService target = spy(new TransferMoneyService());

    doReturn(mockDepositMoneyService)
            .when(target).proxyDepositMoneyServiceCreator();

    doReturn(mockWithdrawMoneyService)
            .when(target).proxyWithdrawMoneyServiceCreator();

Maggiori dettagli nel link sopra.


0

Mockè un doppio oggetto nudo. Questo oggetto ha le stesse firme dei metodi ma la realizzazione è vuota e restituisce il valore predefinito - 0 e null

Spyè un doppio oggetto clonato. Il nuovo oggetto viene clonato in base a un oggetto reale ma hai la possibilità di deriderlo

class A {

    String foo1() {
        foo2();
        return "RealString_1";
    }

    String foo2() {
        return "RealString_2";
    }

    void foo3() {
        foo4();
    }

    void foo4() {

    }
}
@Test
public void testMockA() {

    //given
    A mockA = Mockito.mock(A.class);
    Mockito.when(mockA.foo1()).thenReturn("MockedString");

    //when
    String result1 = mockA.foo1();
    String result2 = mockA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals(null, result2);

    //Case 2
    //when
    mockA.foo3();

    //then
    verify(mockA).foo3();
    verify(mockA, never()).foo4();
}

@Test
public void testSpyA() {
    //given
    A spyA = Mockito.spy(new A());

    Mockito.when(spyA.foo1()).thenReturn("MockedString");

    //when
    String result1 = spyA.foo1();
    String result2 = spyA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals("RealString_2", result2);

    //Case 2
    //when
    spyA.foo3();

    //then
    verify(spyA).foo3();
    verify(spyA).foo4();
}

[Prova doppi tipi]

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.