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

原文由 Royal Rose 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.5k
2 个回答

您将模拟嵌套在模拟中。在完成对 MyMainModel 的模拟之前,您正在调用 getSomeList() ,它会进行一些模拟。当你这样做时,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 的以下交互:

  • 模拟方法 mainModel.getList() 被调用,
  • 静态方法 when 被调用,
  • 方法 thenReturnwhen 方法返回的 OngoingStubbing 对象上被调用。

The thenReturn method can then instruct the mock it received via the OngoingStubbing method to handle any suitable call to the getList method to return someModelList .

事实上,由于 Mockito 看不到你的代码,你也可以将你的 mocking 写成如下:

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

这种风格读起来有点不太清楚,特别是因为在这种情况下 null 必须被强制转换,但它会生成与 Mockito 相同的交互序列,并将获得与上述行相同的结果。

然而,线

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

导致与 Mockito 的以下交互:

  1. 模拟方法 mainModel.getList() 被调用,
  2. 静态方法 when 被调用,
  3. 创建了一个新的 mockSomeModel (在 getSomeList() 内),
  4. 模拟方法 model.getName() 被调用,

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

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

这看起来很傻 Mockito 因为它不能确定你在做什么 mainModel.getList()

请注意,我们没有得到 thenReturn 方法调用,因为 JVM 在调用该方法之前需要评估该方法的参数。在这种情况下,这意味着调用 getSomeList() 方法。

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

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

3:如果完成,您将在“thenReturn”指令之前存根另一个模拟的行为

原文由 Luke Woodward 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

解决方法 1

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

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

对此的解决方案是在变量中创建子 mock,并在父 mock 的范围内使用该变量,以防止 mock 创建被显式嵌套。

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


解决方法 2

确保你所有的模拟都应该有一个 thenReturn

GL

原文由 Braian Coronel 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题