W Mockito wykryto niedokończone zatarcie

151

Podczas przeprowadzania testów otrzymuję następujący wyjątek. Używam Mockito do kpiny. Podpowiedzi, o których wspomina biblioteka Mockito, nie pomagają.

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)
        ..........

Kod testowy od DomainTestFactory. Kiedy uruchamiam następujący test, widzę wyjątek.

@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;
    }
}
Royal Rose
źródło
Cześć Mureinik, zaktualizowałem post z numerami linii
Royal Rose

Odpowiedzi:

371

Kpisz sobie z gniazda wewnątrz kpiny. Dzwonisz getSomeList(), co trochę kpi, zanim skończysz kpićMyMainModel . Mockito nie lubi, kiedy to robisz.

Zastąpić

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

z

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

Aby zrozumieć, dlaczego powoduje to problem, musisz trochę wiedzieć, jak działa Mockito, a także mieć świadomość, w jakiej kolejności wyrażenia i instrukcje są oceniane w Javie.

Mockito nie może odczytać twojego kodu źródłowego, więc aby dowiedzieć się, o co go prosisz, opiera się w dużej mierze na stanie statycznym. Kiedy wywołujesz metodę na obiekcie pozorowanym, Mockito zapisuje szczegóły wywołania na wewnętrznej liście wywołań. whenMetoda odczytuje ostatni z tych inwokacji z listy i zapisuje tę inwokację w OngoingStubbingobiekcie to powraca.

Linia

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

powoduje następujące interakcje z Mockito:

  • Metoda pozorowana mainModel.getList()jest ,
  • Wywołuje się metodę statyczną when,
  • Metoda thenReturnjest wywoływana na OngoingStubbingobiekcie zwróconym przez whenmetodę.

thenReturnSposób może następnie nakazać pozornie jest odbierany za pomocą OngoingStubbingsposobu obsługi dowolnego odpowiedniego połączenia do getListsposobu powrotu someModelList.

W rzeczywistości, ponieważ Mockito nie widzi twojego kodu, możesz również napisać mockowanie w następujący sposób:

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

Ten styl jest nieco mniej czytelny, zwłaszcza że w tym przypadku null musi zostać rzucony, ale generuje tę samą sekwencję interakcji z Mockito i osiąga ten sam wynik, co powyższa linia.

Jednak linia

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

powoduje następujące interakcje z Mockito:

  1. Metoda pozorowana mainModel.getList()jest ,
  2. Metoda statyczna when,
  3. Powstaje nowy mockz SomeModel(wewnątrzgetSomeList() ),
  4. Wywoływana model.getName()jest metoda Mock ,

W tym momencie Mockito jest zdezorientowany. Myślał, że kpisz mainModel.getList(), ale teraz mówisz mu, że chcesz kpić z model.getName()metody. Dla Mockito wygląda na to, że wykonujesz następujące czynności:

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

To wygląda głupio, Mockitoponieważ nie można być pewnym, z czym robisz mainModel.getList().

Zauważ, że nie dotarliśmy do thenReturnwywołania metody, ponieważ maszyna JVM musi ocenić parametry tej metody, zanim będzie mogła wywołać metodę. W tym przypadku oznacza to wywołanie getSomeList()metody.

Ogólnie rzecz biorąc, poleganie na stanie statycznym jest złą decyzją projektową, tak jak robi to Mockito, ponieważ może to prowadzić do przypadków, w których naruszona jest zasada najmniejszego zdziwienia. Jednak projekt Mockito zapewnia jasne i wyraziste kpiny, nawet jeśli czasami prowadzi do zdziwienia.

Wreszcie, ostatnie wersje Mockito dodają dodatkową linię do powyższego komunikatu o błędzie. Ta dodatkowa linia wskazuje, że możesz być w takiej samej sytuacji, jak to pytanie:

3: odrzucasz zachowanie innej makiety w środku przed instrukcją „thenReturn”, jeśli została zakończona

Luke Woodward
źródło
Czy jest jakieś wytłumaczenie tego faktu? Rozwiązanie działa. I nie rozumiem, dlaczego makieta tworzenia „na miejscu” nie działa. Kiedy tworzysz mock i przekazujesz utworzony mock przez odniesienie do innego mock, to działa.
Capacytron
1
Doskonała odpowiedź, kocham TAK! Wieki zajęłoby mi znalezienie tego samemu
Dici
4
Świetna odpowiedź Luke! Bardzo szczegółowe wyjaśnienie prostymi słowami. Dziękuję Ci.
Tomasz Kalkosiński
1
Niesamowite. Zabawne jest to, że kiedy wykonuję wywołanie metody bezpośredniej i powoli debuguję, to działa. Atrybut CGLIB $ BOUND otrzyma wartość true, ale w jakiś sposób zajmuje to trochę czasu. Kiedy używam wywołania metody bezpośredniej i zatrzymuję się przed treningiem (kiedy ...), widzę, że wartość jest najpierw fałszywa, a później staje się prawdą. Jeśli jest fałszywa i rozpoczyna się trening, występuje ten wyjątek.
Michael Hegner
Zrobiłeś mój dzień! To jest rodzaj błędu, który powoduje, że marnujesz dużo czasu! Myślałem, że to coś związanego z kotlinem na początku
Bronx
1
org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
E.g. thenReturn() may be missing.

Aby kpić z metod void, wypróbuj poniższe:

//Kotlin Syntax

 Mockito.`when`(voidMethodCall())
           .then {
                Unit //Do Nothing
            }
takharsh
źródło