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

Posted

技术标签:

【中文标题】在 Mockito 中检测到未完成的存根【英文标题】:Unfinished Stubbing Detected in 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;
    

【问题讨论】:

【参考方案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 的以下交互:

    模拟方法mainModel.getList()被调用, 静态方法when被调用, 在SomeModel 中创建一个新的mock(在getSomeList() 内), 模拟方法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);

检查语法:

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

常见错误如下:

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

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

【讨论】:

以上是关于在 Mockito 中检测到未完成的存根的主要内容,如果未能解决你的问题,请参考以下文章

英华在线助手-自动静音连播-自动跳转到未完成视频-视频暂停超时播放提示音

配置 IntelliJ IDEA,以便在 git 中检测到源但检测到未注册的 Vcs 根

如何在 JUnit Test Java 中替换 Mockito 以存根类

java 无效方法在mockito中存根

颤振中的 mockito 缺少存根错误。尝试在模拟的 SharedPreferences 上使用 setString

Flutter Web:在 Flutter 运行时在注入的 client.js 脚本中检测到未处理的错误