Неоконченное обрушение, обнаруженное в Mockito

Во время выполнения тестов я получаю следующее исключение. Я использую Mockito для насмешки. Рекомендации, упомянутые в библиотеке Mockito, не помогают.

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

Тестовый код из DomainTestFactory. Когда я запускаю следующий тест, я вижу исключение

@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;
    }
}

Ответ 1

Ты гнездился в насмешливости. Вы вызываете getSomeList(), что немного насмехается, прежде чем вы закончите насмешку над MyMainModel. Mockito не нравится, когда вы это делаете.

Заменить

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

с

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

Чтобы понять, почему это вызывает проблему, вам нужно немного узнать о том, как работает Mockito, а также знать, в каком порядке выражаются выражения и выражения в Java.

Mockito не может прочитать ваш исходный код, поэтому, чтобы выяснить, что вы просите его сделать, он много полагается на статическое состояние. Когда вы вызываете метод на макет объекта, Mockito записывает детали вызова во внутренний список вызовов. Метод when считывает последнюю из этих выписок из списка и записывает этот вызов в возвращаемом объекте OngoingStubbing.

Линия

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

вызывает следующие взаимодействия с Mockito:

  • Вызывается метод mock mainModel.getList(),
  • Вызывается статический метод when,
  • Метод thenReturn вызывается в объекте OngoingStubbing, возвращаемом методом when.

Метод thenReturn может затем проинструктировать макет, полученный им с помощью метода OngoingStubbing, для обработки любого подходящего вызова методу getList для возврата someModelList.

Фактически, поскольку Mockito не может видеть ваш код, вы также можете написать свое издевательство следующим образом:

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

Этот стиль несколько менее понятен для чтения, тем более, что в этом случае null должен быть запущен, но он генерирует ту же последовательность взаимодействий с Mockito и достигнет того же результата, что и строка выше.

Однако линия

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

вызывает следующие взаимодействия с Mockito:

  • Вызывается метод mock mainModel.getList(),
  • Вызывается статический метод when,
  • Создается новый mock of SomeModel (внутри getSomeList()),
  • Вызывается метод mock model.getName(),

В этот момент Mockito запутался. Он думал, что вы насмехаетесь mainModel.getList(), но теперь вы говорите, что хотите издеваться над методом model.getName(). Для Mockito похоже, что вы делаете следующее:

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

Это выглядит глупо для Mockito, поскольку он не может быть уверен, что вы делаете с mainModel.getList().

Обратите внимание, что мы не получили вызов метода thenReturn, так как JVM должен оценить параметры этого метода, прежде чем он сможет вызвать метод. В этом случае это означает вызов метода getSomeList().

Как правило, это плохое дизайнерское решение полагаться на статическое состояние, как это делает Мокито, потому что это может привести к тому, что нарушается Принцип наименьшего удивления. Однако дизайн Mockito делает четкое и выразительное издевательство, даже если это иногда приводит к изумлению.

Наконец, последние версии Mockito добавляют дополнительную строку к сообщению об ошибке выше. Эта дополнительная строка указывает, что вы можете быть в той же ситуации, что и этот вопрос:

3: вы завершаете поведение другого макета внутри перед командой "thenReturn", если завершено

Ответ 2

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

Для насмешки над пустыми методами попробуйте ниже:

//Kotlin Syntax

 Mockito.'when'(voidMethodCall())
           .then {
                Unit //Do Nothing
            }