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



我最近看到一些Mockito 1.9.5的代码是这样工作的:

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

因为myObject是不是一个模拟对象,而是一个非模拟类的实例,我很惊讶这个编译和运行没有失败的单元测试。我预计我会得到类似"您要求我在非模拟对象上设置期望,而我期望只在模拟对象上设置期望"这样的失败。

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


Update:添加更多代码,这是实际复制行为所必需的,我发现令人困惑。这些例子充分说明了我的问题。以下代码的行为与我所期望的一样——当我运行这个测试时,测试失败,并显示一条消息

when()需要一个参数,该参数必须是'对mock的方法调用'。

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");
}

可能是难以置信和脆弱的巧合,除非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());

否则,这段代码可能没有按照它看起来应该的方式工作。when的参数是没有意义的,它的用意是隐藏被模拟的交互是对一个模拟的最近的方法调用。例如:

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类将mock作为参数,并且最后的交互是与someMethod交互的mock。
你的编辑证实了我的怀疑。就Java而言,它需要在调用when之前将参数求值为when,因此您的测试调用someMethod (real)。Mockito看不到这一点——它只能在您与它的一个mock交互时采取行动——所以在您的第一个示例中,它看不到与mock的任何交互,因此它失败了。在第二个示例中,someMethod调用doSomething, Mockito 可以看到,因此它返回默认值(null)并将其标记为最近的方法调用。然后发生对when(null)的调用,Mockito忽略参数(null)并引用最近调用的方法(doSomething),并将其存根为从该点开始返回"bogus"。 您可以通过将这个断言添加到您的测试中看到,即使您从未显式地存根它:
assertEquals("bogus", mockAnotherObject.doSomething());

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

相关内容

  • 没有找到相关文章

最新更新