Незаконченный Stubbing обнаружен в Mockito

151

Я получаю следующее исключение при выполнении тестов. Я использую 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;
    }
}
Королевская роза
источник
Привет Mureinik, я обновил пост с номерами строк
Royal Rose

Ответы:

371

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

замещать

@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:

  • Поддельный метод 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:

  1. Поддельный метод mainModel.getList()называется,
  2. Статический метод whenназывается,
  3. Новый mockиз SomeModelсоздан (внутри getSomeList()),
  4. Поддельный метод model.getName()называется,

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

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

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

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

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

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

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

Люк Вудворд
источник
Есть ли объяснение этому факту? Решение работает. И я не понимаю, почему создание макетов «на месте» не работает. Когда вы создаете макет и передаете созданный макет путем ссылки на другой макет, это работает.
Capacytron
1
Отличный ответ, люблю ТАК! Мне потребовались бы годы, чтобы найти это самому
Dici
4
Отличный ответ Люк! Очень подробное объяснение простыми словами. Спасибо.
Томаш Калкосиньски
1
Потрясающие. Самое смешное, что когда я делаю прямой вызов метода и медленно отлаживаю, то это работает. Атрибут CGLIB $ BOUND получит значение true, но каким-то образом это займет немного времени. Когда я использую прямой вызов метода и останавливаюсь перед тренировкой (когда ...), я вижу, что значение сначала ложно, а позже становится истинным. Когда оно ложно и начинается обучение, тогда происходит это исключение.
Майкл Хегнер
Вы сделали мой день! Это ошибка, которая заставляет вас тратить много времени! Я думал, что это было что-то, связанное с kotlin в начале
Бронкс
1
org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
E.g. thenReturn() may be missing.

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

//Kotlin Syntax

 Mockito.`when`(voidMethodCall())
           .then {
                Unit //Do Nothing
            }
takharsh
источник