Mockito 检测到未完成的桩设定

246

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

535

你正在嵌套模拟。在完成对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框架的交互方式:

  • 调用Mock方法mainModel.getList(),
  • 调用静态方法when,
  • when方法返回的OngoingStubbing对象上调用thenReturn方法。

然后,thenReturn方法可以指示通过OngoingStubbing方法接收到的mock处理任何适当的调用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. 创建一个新的SomeModelmock(在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: 您正在before 'thenReturn'指令完成之前存根另一个mock的行为


8
非常好的答案,喜欢SO!如果没有你,我可能会花费很长时间才能找到这个答案。 - Dici
1
太棒了。有趣的是,当我使用直接方法调用并且慢慢调试时,它就可以正常工作。CGLIB $ BOUND属性将获得值true,但不知何故需要一点时间。当我使用直接方法调用,在训练之前停止(当...)时,我看到值首先为false,后来变为true。当它为false并开始训练时,就会出现此异常。 - Michael Hegner
1
很棒的回答,Luke!现在Mockito周围的许多事情都变得清晰了。非常感谢你。 - Arjun Kalidas
我在一个涉及多线程的更复杂场景中遇到了这个错误,而且我似乎没有进行内部模拟。相反,我怀疑Mockito内部存在线程安全问题。有人遇到过这种情况吗? - haelix
在 Jenkins 中构建我的发布时,我遇到了这种问题,在我的本地机器上它运行良好。 如果我理解正确 - 如果我将静态方法用作 thenReturn 的参数,那么没问题吗? - BOTU
显示剩余4条评论

7

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()等。


4

针对使用com.nhaarman.mockitokotlin2.mock {}的用户

解决方法1

当我们在一个 mock 中创建另一个 mock 时,会出现此错误。

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

这个问题的解决方法是创建一个子模拟对象,并将其存储在变量中,在父模拟对象的范围内使用该变量,以防止显式嵌套模拟对象的创建。
val liveDataMock = mock {
        on { y() } doReturn z()
}
mock {
    on { x() } doReturn liveDataMock
}

解决方法2

确保你所有应该有thenReturn的模拟都有了它。

GL


2
同时对于 com.nhaarman.mockitokotlin2.mock,请确保所有应该有 thenReturn 的模拟对象都有 thenReturn。对我来说,导致问题的并不是它所抱怨的模拟对象。 - Naruto Sempai
@NarutoSempai 很好,回答已更新。如果您认为有必要,我邀请您在解决方法2中提供更多细节。谢谢。 - Braian Coronel

2
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
            }

1
我在错误地使用时遇到了相同的错误。
doReturn(value).when(service.getValue());

替代

doReturn(value).when(service).getValue();

0

我对@Luke Woodward的详细回答感到非常兴奋,想要分享一个解决方法。 正如@Luke Woodward所解释的那样,我们不能像下面这样进行两个调用:

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

Than可以在调用链中发生。 但是如果您使用以下结构:

doReturn(mockToken("token3")).when(mock).getAccessToken();

何时

OAuth2AccessToken mockToken(String tokenVal){
        OAuth2AccessToken token = Mockito.mock(OAuth2AccessToken.class);
        doReturn( 60 ).when(token).getExpiresIn();
        doReturn(tokenVal).when(token).getValue();
        return token;
    }

所有的功能都将如预期一样正常工作。


0
我正在使用Kotlin中的Mockito for Android,但是在我的情况下找不到合适的解决方案。
我想要一种方法来模拟一个异常。 当抛出异常时,需要测试代码是否适当地更新为ErrorState的UI状态。
`when`(mockApi.getData()).thenAnswer {
            throw NotConnectedToInternetException()
        }
viewModel.getData()
assertEquals(viewModel.apiResult.value!!.message, "Make sure you have an active network connection") 

当-那么答案可以根据所使用的库替换为给定-将答案

希望这对某人有所帮助


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接