在 Mockito 中检测到未完成的存根

2022-08-31 05:54:11

我在运行测试时遇到以下异常。我正在使用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

你在嘲笑中嵌套了嘲笑。您正在呼叫 ,这会在您完成 的模拟之前进行一些模拟。Mockito不喜欢你这样做。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 会在内部调用列表中记录调用的详细信息。该方法从列表中读取这些调用中的最后一个,并将此调用记录在它返回的对象中。whenOngoingStubbing

生产线

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

导致与Mockito的以下交互:

  • 模拟方法称为,mainModel.getList()
  • 静态方法称为,when
  • 方法在方法返回的对象上调用。thenReturnOngoingStubbingwhen

然后,该方法可以指示它通过该方法接收的模拟处理对该方法的任何合适的调用以返回。thenReturnOngoingStubbinggetListsomeModelList

事实上,由于Mockito看不到你的代码,你也可以按如下方式编写你的模拟:

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

这种风格读起来不太清楚,特别是因为在这种情况下必须投射,但它与Mockito产生相同的交互序列,并将获得与上面行相同的结果。null

但是,该行

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

导致与Mockito的以下交互:

  1. 模拟方法称为,mainModel.getList()
  2. 静态方法称为,when
  3. 创建了一个新的 (内部 ),mockSomeModelgetSomeList()
  4. 模拟方法称为,model.getName()

在这一点上,Mockito感到困惑。它以为你在嘲笑,但现在你告诉它你想嘲笑这个方法。对于Mockito来说,看起来你正在执行以下操作:mainModel.getList()model.getName()

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

这看起来很愚蠢,因为它无法确定您正在做什么。MockitomainModel.getList()

请注意,我们没有进入方法调用,因为 JVM 需要先计算此方法的参数,然后才能调用该方法。在本例中,这意味着调用该方法。thenReturngetSomeList()

一般来说,像Mockito那样依赖静态状态是一个糟糕的设计决策,因为它可能导致违反最小惊讶原则的情况。然而,Mockito的设计确实使清晰和富有表现力的嘲笑,即使它有时会导致惊讶。

最后,最新版本的Mockito在上面的错误消息中添加了一行额外的行。这一行额外的行表示您可能处于与此问题相同的情况:

3:在“然后返回”指令完成之前,您正在对另一个模拟者的行为进行存根


答案 2

对于那些使用com.nhaarman.mockitokotlin2.mock {}

解决方法 1

例如,当我们在另一个模拟中创建一个模拟时,会发生此错误

mock {
    on { x() } doReturn mock {
        on { y() } doReturn z()
    }
}

此问题的解决方案是在变量中创建子模拟,并在父模拟的作用域中使用该变量,以防止模拟创建显式嵌套。

val liveDataMock = mock {
        on { y() } doReturn z()
}
mock {
    on { x() } doReturn liveDataMock
}

解决方法 2

确保所有应具有 .thenReturn

断续器


推荐