当前位置: 首页 > 知识库问答 >
问题:

为什么Mockito“when”在非模拟对象上工作?

姚昊焱
2023-03-14

我最近看到了一些Mockito 1.9.5代码,其工作原理如下:

MyObject myObject = new MyObject();
...
Mockito.when(myObject.someMethod()).thenReturn("bogus");

由于myObject不是一个模拟对象,而是一个非模拟类的实例,所以我很惊讶,它编译并运行时没有通过单元测试。我希望我会遇到一个失败,比如“你让我在非模拟对象上设置一个期望值,而我希望只在模拟对象上设定期望值。”

为什么这段代码不会导致测试失败?

更新:添加更多的代码,这是实际复制我感到困惑的行为所必需的。这些例子充分说明了我的问题。下面的代码如我所料——当我运行这个测试时,测试失败,并显示一条消息

when()需要一个必须是“模拟上的方法调用”的参数。

public class AnotherObject{
    public String doSomething(){
        return "did something";
    };
}

public class MyObject{
    private AnotherObject anotherObject = new AnotherObject();

    public void setAnotherObject(AnotherObject anotherObject) {
        this.anotherObject = anotherObject;
    }

    public String someMethod(){
        return anotherObject.doSomething();
    }
}

@Test
public void WhyDoesWhenWorkOnNonMock() throws Exception {
    MyObject myObject = new MyObject();
    Mockito.when(myObject.someMethod()).thenReturn("bogus");
}

现在,如果我在这个设计好的测试中添加几行特定的代码,测试将不再失败,尽管我预计会出现与之前相同的失败和消息:

public class AnotherObject{
    public String doSomething(){
        return "did something";
    };
}

public class MyObject{
    private AnotherObject anotherObject = new AnotherObject();

    public void setAnotherObject(AnotherObject anotherObject) {
        this.anotherObject = anotherObject;
    }

    public String someMethod(){
        return anotherObject.doSomething();
    }
}

@Test
public void WhyDoesWhenWorkOnNonMock() throws Exception {
    MyObject myObject = new MyObject();
    AnotherObject mockAnotherObject = Mockito.mock(AnotherObject.class);
    myObject.setAnotherObject(mockAnotherObject);
    Mockito.when(myObject.someMethod()).thenReturn("bogus");
}

共有1个答案

孙泉
2023-03-14

可能是不可思议且脆弱的巧合,除非myObject实际上被设置为间谍。

Mockito允许创建真实对象的“间谍”:

MyObject myObject = spy(new MyObject());
Mockito.when(myObject.someMethod()).thenReturn("something");

// myObject is actually a duplicate of myObject, where all the fields are copied
// and the methods overridden. By default, Mockito silently records interactions.

myObject.foo(1);
verify(myObject).foo(anyInt());

// You can stub in a similar way, though doReturn is preferred over thenReturn
// to avoid calling the actual method in question.

doReturn(42).when(myObject).bar();
assertEquals(42, myObject.bar());

除此之外,这段代码可能没有按照它看起来应该的方式工作。的参数没有意义时,糖是为了隐藏模拟的交互是对模拟的最新方法调用。例如:

SomeObject thisIsAMock = mock(SomeObject.class);
OtherObject notAMock = new OtherObject();

thisIsAMock.methodOne();
Mockito.when(notAMock.someOtherMethod()).thenReturn("bar");
// Because notAMock isn't a mock, Mockito can't see it, so the stubbed interaction
// is the call to methodOne above. Now methodOne will try to return "bar",
// even if it isn't supposed to return a String at all!

像这样的不匹配很容易成为ClassCastException,InvalidUseOfMatchersException和其他奇怪错误的来源。也有可能你的真正的MyObject类将模拟作为参数,并且最后一次交互是与someMethod交互的模拟。

您的编辑证实了我的怀疑。就Java而言,它需要先计算参数到何时,然后才能调用何时,因此您的测试调用someMethod(real)。Mockito看不到这一点 - 它只能在您与其中一个模拟交互时采取行动 - 因此在您的第一个示例中,它看到与模拟的交互为零,因此失败。在第二个示例中,您的someMethod调用doSomething,Mockito可以看到,因此它返回默认值(null)并将其标记为最新的方法调用。然后调用when(null)发生,Mockito忽略参数(null)并引用最近调用的方法(doSomething),并存根以从该点开始返回“bogus”。

通过将这个断言添加到您的测试中,您可以看到这一点,即使您从未显式地删除它:

assertEquals("bogus", mockAnotherObject.doSomething());

作为额外的参考,我在Mockito matchers上写了一个单独的SO答案,其中的实现细节可能会有用。有关类似问题的扩展视图,请参见步骤5和6。

 类似资料:
  • 我正在使用JUnit5和Mockito为一个类。 依赖类被注入到。 这是我的: 中的abc()方法调用执行一些操作。 我不想在测试中调用这个原始的externalApi方法。我需要定义吗 或者它只是够嘲笑? 当我们不定义调用模拟对象的方法时会发生什么?

  • 我需要测试一个服务类,但是当我试图模拟dao类时,它没有被触发,因此不能使用ThenReturn()。 我认为问题是因为我在服务类(Spring MVC 3.1)中为我的Dao和@Autowired使用了一个接口: 接口: 执行情况: 成功了!

  • 有许多方法可以使用mockito初始化模拟对象。其中什么是最好的方法? 1. 建议我有没有比这些更好的方法...

  • 问题内容: 有很多方法可以使用MockIto初始化模拟对象。其中最好的方法是什么? 1。 2。 [编辑] 3。 如果有其他方法可以建议我… 问题答案: 对于模拟初始化,使用或是严格等效的解决方案。从MockitoJUnitRunner的javadoc中: 当你已经在测试用例上配置了特定的运行器时,可以使用第一个解决方案(带有)。 第二个解决方案(带有)更经典,也是我的最爱。代码更简单。使用转轮提供

  • 我有下课情节。在测试MyTestableClass时,我希望处理Autowired类。我只想模拟AutoWired类中的变量。 示例类如下- 例如,Console在返回true之前应该给我“inside Service class”。 提前道谢!

  • 我有一段类似这样的代码: 我的测试是: 上面的代码不应该抛出NPE吗? 当我将测试更改为: 然后我的代码正确地抛出了一个空指针异常。 我的测试第一版和第二版有什么区别? 在我的第一个版本中,我告诉mockA返回mockB。但是我也将mockB设置为null,所以当调用b.doSomething()时,我认为该方法应该抛出一个空指针,因为它试图调用null.doSomething()。 我知道我应该