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

问题描述 投票:0回答:7

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

你将嘲笑嵌套在嘲笑之中。在完成对

getSomeList()
的嘲笑之前,您正在调用
MyMainModel
,它会进行一些嘲笑。 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
  • 方法
    thenReturn
    OngoingStubbing
    方法返回的
    when
    对象上调用。

thenReturn
方法然后可以指示它通过
OngoingStubbing
方法接收到的模拟来处理对
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. 创建了
    mock
    的新
    SomeModel
    (在
    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”指令之前对另一个模拟的行为进行存根


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 {
    on { x() } doReturn mock {
        on { y() } doReturn z()
    }
}

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

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

解决方法2

确保所有模拟都应该有

thenReturn

GL


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
            }

0
投票

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

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

这可能发生在调用链中。 但如果您将使用构造:

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
投票

我误用的时候也出现了同样的错误:

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

而不是

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

0
投票

我在 Kotlin 中使用 Android 版 Mockito,但找不到适合我的情况的解决方案。

我想要一种模拟异常的方法。 必须测试代码,当抛出异常时 UI 状态是否适当地更新为 ErrorState

`when`(mockApi.getData()).thenAnswer {
            throw NotConnectedToInternetException()
        }
viewModel.getData()
assertEquals(viewModel.apiResult.value!!.message, "Make sure you have an active network connection") 

when-thenAnswer 可以替换为给定的willAnswer,具体取决于所使用的库

希望这对某人有帮助

© www.soinside.com 2019 - 2024. All rights reserved.