【问题标题】:Unfinished Stubbing Detected in Mockito在 Mockito 中检测到未完成的存根
【发布时间】:2014-12-06 18:05:10
【问题描述】:

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

【问题讨论】:

    标签: java mocking mockito


    【解决方案1】:

    您将模拟嵌套在模拟中。在完成对 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被调用,
    • when 方法返回的OngoingStubbing 对象上调用方法thenReturn

    然后thenReturn 方法可以指示它通过OngoingStubbing 方法接收到的模拟来处理对getList 方法的任何适当调用以返回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. SomeModel 中创建一个新的mock(在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”指令之前对内部另一个模拟的行为进行存根

    【讨论】:

    • 优秀的答案,爱!我自己要花很长时间才能找到这个
    • 卢克的好答案!用简单的话非常详细的解释。谢谢。
    • 太棒了。有趣的是,当我进行直接方法调用并缓慢调试时,它就可以工作了。 CGLIB$BOUND 的 Attribute 将获得值 true,但不知何故需要一点时间。当我使用直接方法调用并在训练前停止时(当...),然后我看到该值首先为假,然后变为真。当它为假并开始训练时,就会发生此异常。
    • 你成就了我的一天!这种错误会让你浪费很多时间!一开始我以为是和kotlin有关的东西
    • 优秀的答案。拯救了我一整天。
    【解决方案2】:

    对于那些使用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 的模拟具有thenReturn

    GL

    【讨论】:

    • 对于com.nhaarman.mockitokotlin2.mock,确保所有你的模拟应该有一个thenReturn有一个thenReturn。对我来说,问题不是它所抱怨的嘲笑。
    • @NarutoSempai Perfect,答案已更新。如果您认为有必要,我邀请您在解决方法 2 中描述更多细节。谢谢
    【解决方案3】:
    org.mockito.exceptions.misusing.UnfinishedStubbingException: 
    Unfinished stubbing detected here:
    E.g. thenReturn() may be missing.
    

    要模拟 void 方法,请尝试以下:

    //Kotlin Syntax
    
     Mockito.`when`(voidMethodCall())
               .then {
                    Unit //Do Nothing
                }
    

    【讨论】:

      【解决方案4】:

      请阅读这个article 它有特殊的解释和工作方法。此外,该问题的另一个可能原因和解决方案是将实际模拟移至测试的早期阶段。

      【讨论】:

      • 请总结链接的文章。
      【解决方案5】:

      AbcService abcService = mock(AbcService.class);

      检查语法:

      1. doThrow(new RunTimeException()).when(abcService).add(any(), any())

      常见错误如下:

      A. doThrow(new RunTimeException()).when(abcService.add(any(), any()))

      同样,检查when().thenReturn()等。

      【讨论】:

        猜你喜欢
        • 2020-09-07
        • 1970-01-01
        • 1970-01-01
        • 2021-12-06
        • 1970-01-01
        • 1970-01-01
        • 2021-07-14
        • 1970-01-01
        相关资源
        最近更新 更多