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

Why does Mockito "when" work on a non-mock object?

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

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

因为 myObject 不是 一个模拟对象,而是一个非模拟对象的实例 class,我很惊讶这个编译和 运行 没有单元测试失败。我以为我会说 "You asked me to set up an expectation on a non-mock object, and I expected to set expectations only on mock objects."

之类的失败

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


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

when() requires an argument which has to be 'a method call on a 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 允许创建 "spy" 真实对象:

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 class 将模拟作为参数,最后一次交互是与 someMethod 交互的模拟。


您的编辑证实了我的怀疑。就 Java 而言,它需要先将参数计算为 when,然后才能调用 when,因此您的测试调用 someMethod(真实)。 Mockito 看不到这一点——它只能在你与它的一个模拟交互时采取行动——所以在你的 first 示例中,它看到与模拟的交互为零,因此它失败了。在您的 second 示例中,您的 someMethod 调用 doSomething,Mockito 可以 看到,所以它 returns默认值 (null) 并将其标记为最近的方法调用。然后调用 when(null),Mockito 忽略参数(null)并引用最近调用的方法(doSomething),并将其存根到 return "bogus"点上。

您可以通过将此断言添加到您的测试中看到这一点,即使您从未明确地对它进行存根:

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

作为额外的参考,我写了 a separate SO answer on Mockito matchers,其中的实现细节可能会有用。有关类似问题的扩展视图,请参阅步骤 5 和 6。