Stubbing incompiuto rilevato a Mockito


151

Ricevo le seguenti eccezioni durante l'esecuzione dei test. Sto usando Mockito per deridere. I suggerimenti menzionati dalla biblioteca Mockito non aiutano.

org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
    -> at com.a.b.DomainTestFactory.myTest(DomainTestFactory.java:355)

    E.g. thenReturn() may be missing.
    Examples of correct stubbing:
        when(mock.isOk()).thenReturn(true);
        when(mock.isOk()).thenThrow(exception);
        doThrow(exception).when(mock).someVoidMethod();
    Hints:
     1. missing thenReturn()
     2. you are trying to stub a final method, you naughty developer!

        at a.b.DomainTestFactory.myTest(DomainTestFactory.java:276)
        ..........

Codice test da DomainTestFactory. Quando eseguo il seguente test, vedo l'eccezione.

@Test
public myTest(){
    MyMainModel mainModel =  Mockito.mock(MyMainModel.class);
    Mockito.when(mainModel.getList()).thenReturn(getSomeList()); // Line 355
}

private List<SomeModel> getSomeList() {
    SomeModel model = Mockito.mock(SomeModel.class);
    Mockito.when(model.getName()).thenReturn("SomeName"); // Line 276
    Mockito.when(model.getAddress()).thenReturn("Address");
    return Arrays.asList(model);
}

public class SomeModel extends SomeInputModel{
    protected String address;
    protected List<SomeClass> properties;

    public SomeModel() {
        this.Properties = new java.util.ArrayList<SomeClass>(); 
    }

    public String getAddress() {
        return this.address;
    }

}

public class SomeInputModel{

    public NetworkInputModel() {
        this.Properties = new java.util.ArrayList<SomeClass>(); 
    }

    protected String Name;
    protected List<SomeClass> properties;

    public String getName() {
        return this.Name;
    }

    public void setName(String value) {
        this.Name = value;
    }
}

Ciao Mureinik, ho aggiornato il post con i numeri di riga
Royal Rose,

Risposte:


371

Stai annidando beffardo dentro beffardo. Stai chiamando getSomeList(), il che fa un po 'beffardo, prima di aver finito il deridere per MyMainModel. A Mockito non piace quando lo fai.

Sostituire

@Test
public myTest(){
    MyMainModel mainModel =  Mockito.mock(MyMainModel.class);
    Mockito.when(mainModel.getList()).thenReturn(getSomeList()); --> Line 355
}

con

@Test
public myTest(){
    MyMainModel mainModel =  Mockito.mock(MyMainModel.class);
    List<SomeModel> someModelList = getSomeList();
    Mockito.when(mainModel.getList()).thenReturn(someModelList);
}

Per capire perché questo causa un problema, devi sapere un po 'su come funziona Mockito e anche sapere in che ordine espressioni ed istruzioni vengono valutate in Java.

Mockito non può leggere il tuo codice sorgente, quindi per capire cosa gli stai chiedendo di fare, si basa molto sullo stato statico. Quando si chiama un metodo su un oggetto simulato, Mockito registra i dettagli della chiamata in un elenco interno di invocazioni. Il whenmetodo legge l'ultima di queste invocazioni dall'elenco e registra questa invocazione OngoingStubbingnell'oggetto che restituisce.

La linea

Mockito.when(mainModel.getList()).thenReturn(someModelList);

provoca le seguenti interazioni con Mockito:

  • mainModel.getList()Si chiama metodo simulato ,
  • Il metodo statico whenè chiamato,
  • Il metodo thenReturnviene chiamato OngoingStubbingsull'oggetto restituito dal whenmetodo.

Il thenReturnmetodo può quindi istruire il mock che ha ricevuto tramite il OngoingStubbingmetodo per gestire qualsiasi chiamata adatta al getListmetodo da restituire someModelList.

Infatti, poiché Mockito non può vedere il tuo codice, puoi anche scrivere il tuo derisione come segue:

mainModel.getList();
Mockito.when((List<SomeModel>)null).thenReturn(someModelList);

Questo stile è un po 'meno chiaro da leggere, soprattutto perché in questo caso nulldeve essere lanciato, ma genera la stessa sequenza di interazioni con Mockito e otterrà lo stesso risultato della riga sopra.

Tuttavia, la linea

Mockito.when(mainModel.getList()).thenReturn(getSomeList());

provoca le seguenti interazioni con Mockito:

  1. mainModel.getList()Si chiama metodo simulato ,
  2. Il metodo statico whenè chiamato,
  3. Viene creato un nuovo mockdi SomeModel(interno getSomeList()),
  4. model.getName()Si chiama metodo simulato ,

A questo punto Mockito viene confuso. Pensava che tu stessi prendendo in giro mainModel.getList(), ma ora stai dicendo che vuoi deridere il model.getName()metodo. A Mockito, sembra che tu stia facendo quanto segue:

when(mainModel.getList());
// ...
when(model.getName()).thenReturn(...);

Sembra sciocco perché Mockitonon si può essere sicuri di cosa stai facendo mainModel.getList().

Si noti che non siamo riusciti a thenReturnchiamare il metodo, poiché la JVM deve valutare i parametri per questo metodo prima di poter chiamare il metodo. In questo caso, questo significa chiamare il getSomeList()metodo.

Generalmente è una cattiva decisione progettuale affidarsi allo stato statico, come fa Mockito, perché può portare a casi in cui viene violato il principio del minimo stupore. Tuttavia, il design di Mockito rende il beffardo chiaro ed espressivo, anche se a volte porta a stupore.

Infine, le versioni recenti di Mockito aggiungono una riga aggiuntiva al messaggio di errore sopra riportato. Questa riga aggiuntiva indica che potresti trovarti nella stessa situazione di questa domanda:

3: stai soffocando il comportamento di un altro finto dentro prima dell'istruzione 'thenReturn' se completata


C'è qualche spiegazione di questo fatto? La soluzione funziona. E non capisco perché la creazione simulata "sul posto" non funzioni. Quando si crea il mock e si passa al mock creato facendo riferimento ad altri mock, funziona.
Capacytron,

1
Ottima risposta, amore SO! Mi ci sarebbero voluti anni per trovarlo da solo
Dici,

4
Ottima risposta Luca! Spiegazione molto dettagliata in parole semplici. Grazie.
Tomasz Kalkosiński,

1
Eccezionale. La cosa divertente è che quando eseguo la chiamata diretta al metodo ed eseguo il debug lentamente, allora funziona. L'attributo di CGLIB $ BOUND otterrà il valore vero, ma in qualche modo ci vuole un po 'di tempo. Quando uso il metodo diretto chiama e mi fermo prima dell'allenamento (quando ...), allora vedo che il valore è prima falso e poi diventa vero. Quando è falso e inizia l'allenamento, si verifica questa eccezione.
Michael Hegner,

Mi hai reso felice! Questo è il tipo di errore che ti fa perdere un sacco di tempo! All'inizio pensavo fosse qualcosa legato al kotlin
Bronx,

1
org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
E.g. thenReturn() may be missing.

Per deridere i metodi vuoti, prova di seguito:

//Kotlin Syntax

 Mockito.`when`(voidMethodCall())
           .then {
                Unit //Do Nothing
            }
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.