Mockito - 模拟混凝土类

mhs*_*ams 7 java unit-testing mocking mockito

给出以下代码:

    LinkedList list = mock(LinkedList.class);
    doCallRealMethod().when(list).clear();
    list.clear();
Run Code Online (Sandbox Code Playgroud)

通过执行此测试,从LinkedList#clear的第一行抛出NullPointerException:

public void clear() {
    Entry<E> e = header.next;
    while (e != header) {
        Entry<E> next = e.next;
        //Code omitted. 
Run Code Online (Sandbox Code Playgroud)

但是标题之前已被实例化:

private transient Entry<E> header = new Entry<E>(null, null, null);
Run Code Online (Sandbox Code Playgroud)

有人可以解释模拟创作过程中发生了什么吗?

#######更新.######

在阅读了所有答案,特别是Ajay的答案之后,我查看了Objenesis源代码并发现它使用Reflection API来创建代理实例(通过CGLIB),因此绕过层次结构中的所有构造函数,直到java.lang.Object.

以下是模拟问题的示例代码:

public class ReflectionConstructorTest {

    @Test
    public void testAgain() {

        try {
            //java.lang.Object default constructor
            Constructor javaLangObjectConstructor = Object.class
                    .getConstructor((Class[]) null);
            Constructor mungedConstructor = ReflectionFactory
                    .getReflectionFactory()
                    .newConstructorForSerialization(CustomClient.class, javaLangObjectConstructor);

            mungedConstructor.setAccessible(true);

            //Creates new client instance without calling its constructor
            //Thus "name" is not initialized.
            Object client = mungedConstructor.newInstance((Object[]) null);

            //this will print "CustomClient" 
            System.out.println(client.getClass());
            //this will print "CustomClient: null". name is null.
            System.out.println(client.toString());

        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}


class CustomClient {
    private String name;

    CustomClient() {
        System.out.println(this.getClass().getSimpleName() + " - Constructor");
        this.name = "My Name";
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + ": " + name;
    }
}
Run Code Online (Sandbox Code Playgroud)

the*_*dam 8

你只是要求Mockito清楚地打电话给真实的东西,底层物品仍然是Mockito为你创造的假货.如果你需要一个真正的LinkedList,那么只需使用LinkedList - 只有BDD最热门的纯粹主义者会告诉你模仿你周围的一切.我的意思是,你不是在嘲笑字符串是你吗?

Mockito作者自己曾说过,调用真实的东西几乎不应该使用,通常只用于测试遗留代码.

如果您需要监视真实对象(跟踪调用),那么Mockito也有这样的功能:

List list = new LinkedList();
List spy = spy(list);
Run Code Online (Sandbox Code Playgroud)

使用间谍,如果需要,您仍然可以存根方法.它基本上像模拟,但不是;)

  • 无意冒犯:)只是想指出,有时,有些答案不是回答问题,而是质疑OP的行动方针。有时,这些做法会遭到否决。我发现在回答中首先告诉OP为什么它不起作用(从而回答问题),然后提出替代方案(如果我有一个可以提供的话)是一个很好的做法。编辑完成后,也为您+1。 (2认同)

Aja*_*rge 6

你的推理完美无瑕.
关键问题是您没有对实际LinkedList对象进行操作.以下是幕后发生的事情:

Mockito提供的对象是CGLIB库中mock()Enhancer对象.

对我来说就是这样的 java.util.LinkedList$$EnhancerByMockitoWithCGLIB$$cae81a28

哪种行为类似于代理,尽管字段设置为默认值.(null,0等)

  • @AjayGeorge即使从真实代码中抛出异常,该实例仍然是一个 Mockito 模拟,并且 Mockito 正在使用技巧(通过 Objenesis)绕过任何模拟对象的默认构造函数(以便能够使用私有构造函数)。此外,模拟永远不会初始化字段,因为它不应该这样做。您想要实现的是部分模拟,您可以通过 [`spy`](http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html#spy(T) 来实现)。另请注意,部分模拟通常表明测试代码中存在设计缺陷。 (2认同)