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

2022-01-08 00:00:00 mocking mockito java

我在运行测试时遇到以下异常.我正在使用 Mockito 进行模拟.Mockito 库提到的提示没有帮助.

I am getting following exception while running the tests. I am using Mockito for mocking. The hints mentioned by Mockito library are not helping.

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 Code from DomainTestFactory. When I run the following test, I see the exception.

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

推荐答案

你在 mocking 中嵌套了 mocking.在完成对 MyMainModel 的模拟之前,您正在调用 getSomeList(),它会进行一些模拟.当你这样做时,Mockito 不喜欢它.

You're nesting mocking inside of mocking. You're calling getSomeList(), which does some mocking, before you've finished the mocking for MyMainModel. Mockito doesn't like it when you do this.

替换

@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 中表达式和语句的计算顺序.

To understand why this causes a problem, you need to know a little about how Mockito works, and also be aware in what order expressions and statements are evaluated in Java.

Mockito 无法读取您的源代码,因此为了弄清楚您要求它做什么,它在很大程度上依赖于静态状态.当您在模拟对象上调用方法时,Mockito 会将调用的详细信息记录在调用的内部列表中.when 方法从列表中读取这些调用中的最后一个,并将此调用记录在它返回的 OngoingStubbing 对象中.

Mockito can't read your source code, so in order to figure out what you are asking it to do, it relies a lot on static state. When you call a method on a mock object, Mockito records the details of the call in an internal list of invocations. The when method reads the last of these invocations off the list and records this invocation in the OngoingStubbing object it returns.

线

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

导致与 Mockito 的以下交互:

causes the following interactions with Mockito:

  • Mock 方法 mainModel.getList() 被调用,
  • 静态方法被调用时,
  • 方法 thenReturnwhen 方法返回的 OngoingStubbing 对象上调用.
  • Mock method mainModel.getList() is called,
  • Static method when is called,
  • Method thenReturn is called on the OngoingStubbing object returned by the when method.

thenReturn 方法然后可以指示它通过 OngoingStubbing 方法接收到的模拟来处理对 getList 方法的任何合适的调用以返回 <代码>someModelList.

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:

In fact, as Mockito can't see your code, you can also write your mocking as follows:

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

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

This style is somewhat less clear to read, especially since in this case the null has to be casted, but it generates the same sequence of interactions with Mockito and will achieve the same result as the line above.

但是,行

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

导致与 Mockito 的以下交互:

causes the following interactions with Mockito:

  1. Mock 方法 mainModel.getList() 被调用,
  2. 静态方法被调用时,
  3. SomeModel 的新 mock 已创建(在 getSomeList() 内),
  4. 调用模拟方法model.getName()
  1. Mock method mainModel.getList() is called,
  2. Static method when is called,
  3. A new mock of SomeModel is created (inside getSomeList()),
  4. Mock method model.getName() is called,

此时 Mockito 感到困惑.它以为你在模拟 mainModel.getList(),但现在你告诉它你想模拟 model.getName() 方法.对于 Mockito,您似乎正在执行以下操作:

At this point Mockito gets confused. It thought you were mocking mainModel.getList(), but now you're telling it you want to mock the model.getName() method. To Mockito, it looks like you're doing the following:

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

这对于 Mockito 来说看起来很傻,因为它无法确定您在用 mainModel.getList() 做什么.

This looks silly to Mockito as it can't be sure what you're doing with mainModel.getList().

请注意,我们没有到达 thenReturn 方法调用,因为 JVM 需要先评估该方法的参数,然后才能调用该方法.在这种情况下,这意味着调用 getSomeList() 方法.

Note that we did not get to the thenReturn method call, as the JVM needs to evaluate the parameters to this method before it can call the method. In this case, this means calling the getSomeList() method.

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

Generally it is a bad design decision to rely on static state, as Mockito does, because it can lead to cases where the Principle of Least Astonishment is violated. However, Mockito's design does make for clear and expressive mocking, even if it leads to astonishment sometimes.

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

Finally, recent versions of Mockito add an extra line to the error message above. This extra line indicates you may be in the same situation as this question:

3:如果完成,您将在thenReturn"指令之前对内部另一个模拟的行为进行存根

3: you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed

相关文章