为什么Mockito"何时"处理非模拟对象?

Sha*_*awn 5 unit-testing mocking mockito

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

MyObject myObject = new MyObject();
...
Mockito.when(myObject.someMethod()).thenReturn("bogus");
Run Code Online (Sandbox Code Playgroud)

由于myObject 不是一个模拟对象,而是一个非模拟类的实例,我很惊讶这个编译并运行而不会失败单元测试.我预计我会失败说"你让我对非模拟对象设置期望,我希望只对模拟对象设置期望."

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


更新:添加更多代码来实际复制我发现令人困惑的行为.这些例子充分说明了我的问题.以下代码的行为与我预期的一样 - 当我运行此测试时,测试失败并显示一条消息

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");
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我为这个人为的测试添加一些特定的行,那么即使我期望与以前相同的失败和相同的消息,测试也不会失败:

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");
}
Run Code Online (Sandbox Code Playgroud)

Jef*_*ica 13

通过令人难以置信和脆弱的巧合,可能,除非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());
Run Code Online (Sandbox Code Playgroud)

除此之外,这段代码可能不像它应该的那样工作.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!
Run Code Online (Sandbox Code Playgroud)

像这样的不匹配可能是ClassCastException,InvalidUseOfMatchersException和其他奇怪错误的简单来源.您的真实MyObject类也可能将mock作为参数,并且最后一次交互是与与之交互的模拟someMethod.


你的编辑证实了我的怀疑.就Java而言,它需要when在它可以调用之前评估参数when,因此您的测试调用someMethod(实际).Mockito看不到它 - 它只能在你与其中一个模拟器进行交互时采取行动 - 所以在你的第一个例子中,它看到与模拟的零交互,因此它失败了.在你的第二个例子中,你的someMethod调用doSomething是Mockito 可以看到的,所以它返回默认值(null)并将其标记为最近的方法调用.然后调用when(null)发生,Mockito忽略参数(null)并引用最近调用的方法(doSomething),以及从那一点返回"bogus"的存根.

您可以通过将此断言添加到测试中来查看,即使您从未明确地将其断言:

assertEquals("bogus", mockAnotherObject.doSomething());
Run Code Online (Sandbox Code Playgroud)

为了进一步参考,我在Mockito匹配器上了一个单独的SO答案,其实现细节可能很有用.有关类似问题的扩展视图,请参阅步骤5和6.