使用Mockito模块化地连续方法调用

时间:2012-10-27 10:28:22

标签: java mocking mockito

我正在尝试使用Mockito来模拟“Reader”类型类。想想数据流读取器,它有读取各种数据类型的方法,并在每次读取后推进内部指针。

public interface Reader {
    int readInt();
    short readShort();
}

正在测试的类从数据流中读取各种数据结构。例如,

public class Somethings {
    public List<Something> somethings;

    public Somethings(Reader reader) {
        somethings = new List<Something>();
        int count = reader.readInt();
        for (int i=0; i<count; i++) {
            somethings.add(readSomething(reader));
        }
    }

    private Something readSomething(Reader reader) {
        int a = reader.readInt();
        short b = reader.readShort();
        int c = reader.readInt();
        return new Something(a, b, c);
    }
}

最后,我有我的考试:

public class SomethingsTest {
    @Test
    public void testSomethings() {
        Reader reader = Mockito.mock(Reader.class);

        readCount(reader, 2);
        readSomething(reader, 1, 2, 3);
        readSomething(reader, 4, 5, 6);

        Somethings somethings = new Somethings(reader);
        Assert.assertEqual(2, somethings.size());
        Assert.assertEquals(new Something(1, 2, 3), somethings.somethings.get(0));
        Assert.assertEquals(new Something(4, 5, 6), somethings.somethings.get(1));
    }

    private void readCount(Reader reader, int count) {
        when(reader.readInt()).thenReturn(count);
    }

    private void readSomething(Reader reader, int a, short b, int c) {
        when(reader.readInt()).thenReturn(a);
        when(reader.readShort()).thenReturn(b);
        when(reader.readInt()).thenReturn(c);
    }
}

不幸的是,这不起作用。 reader.readInt()始终为每次调用返回6。我明白为什么它会返回6.这不是我的问题。

我可以考虑两种方法来修复测试,但我并不特别喜欢这两种方法。

第一个选项是:

public class SomethingsTest {
    @Test
    public void testSomethings() {
        Reader reader = Mockito.mock(Reader.class);

        when(reader.readInt())
            .thenReturn(2)
            .thenReturn(1)
            .thenReturn(3)
            .thenReturn(4)
            .thenReturn(6);
        when(reader.readShort())
            .thenReturn(2)
            .thenReturn(5);

        Somethings somethings = new Somethings(reader);
        Assert.assertEqual(2, somethings.size());
        Assert.assertEquals(new Something(1, 2, 3), somethings.somethings.get(0));
        Assert.assertEquals(new Something(4, 5, 6), somethings.somethings.get(1));
    }
}

这应该可行,但它非常单一和混乱。很难看出哪个结构的回归是哪个,因为它们都是混合的,没有结构。

我能想到的第二个选择是:

public class SomethingsTest {
    @Test
    public void testSomethings() {
        Reader reader = Mockito.mock(Reader.class);

        NewOngoingStubbing readIntStub = when(reader.readInt());
        NewOngoingStubbing readShortStub = when(reader.readShort());

        readCount(readIntStub, 2);
        readSomething(readIntStub, readShortStub, 1, 2, 3);
        readSomething(readIntStub, readShortStub, 4, 5, 6);

        Somethings somethings = new Somethings(reader);
        Assert.assertEqual(2, somethings.size());
        Assert.assertEquals(new Something(1, 2, 3), somethings.somethings.get(0));
        Assert.assertEquals(new Something(4, 5, 6), somethings.somethings.get(1));
    }

    private void readCount(NewOngoingStubbing readIntStub, int count) {
        readIntStub.thenReturn(count);
    }

    private void readSomething(NewOngoingStubbing readIntStub,
            NewOngoingStubbing  readShortStub, int a, short b, int c) {
        readIntStub.thenReturn(a);
        readShortStub.thenReturn(b);
        readIntStub.thenReturn(c);
    }
}

这至少维护了原始的结构,但是必须为要在stubed对象上进行的每个方法调用传递一个单独的对象是......呃。

执行此测试的最简洁方法是什么?我在这里找不到一些选择吗?我可以利用哪些功能?我今晚刚开始使用Mockito ..所以我很可能会错过一些东西。

3 个答案:

答案 0 :(得分:5)

Mockito本身就在继续进行。你的第一个例子很好,但这也应该有效:

when(reader.readInt()).thenReturn(2, 1, 3, 4, 6);

它的文档是here

如果你有处理特别复杂的交互的东西,可以推出你自己的存根类。您可能会发现使用真实数据初始化一些假,然后使用它,提供了比Mockito更清晰的示例。如果是这样的话,那就明确一点。 Mockito是IMO最好的模拟框架,但有时候我还是自己动手。

答案 1 :(得分:2)

您可以创建一个类来处理它,例如

private static class ReaderStubber {
    private final Reader reader = Mockito.mock(Reader.class);
    private final NewOngoingStubbing readIntStub = when(reader.readInt());;
    private final NewOngoingStubbing readShortStub = when(reader.readShort());;

    public Reader getReader() {
        return reader;
    }    

    private void readCount(int count) {
        readIntStub.thenReturn(count);
    }

    private void readSomething(int a, short b, int c) {
        readIntStub.thenReturn(a);
        readShortStub.thenReturn(b);
        readIntStub.thenReturn(c);
    }   
}

但问题是,你真的需要和Mockito一起做吗?并非一切都应该被嘲笑。也许只是为测试实现一个存根Reader,内部有一些List<Integer>更好。


(编辑) 此外,如果这是可能的,也许您应该重新设计Reader以使其不可变并返回一些NewOngoingReading。经常(但不总是)难以测试的东西更适合重新设计。此外,您不需要处理同步。

答案 2 :(得分:2)

当Mockito的标准方法没有提供模拟您行为的机制时,您可以使用自己的Answer。这是更多的工作,但提供了额外的灵活性。

根据您的具体要求,您可以创建一个Answer,从数字列表中返回一个新元素,而不管请求类型(intshort)。变量readList可以是您可以从用于设置结果的所有函数中访问的成员。

final List<Integer> readList = new ArrayList<>();
// ... Fill readList with answers

Answer answerFromList = new Answer() {
    Object answer(InvocationOnMock invocation) {
        // Remove and return first element
        return readList.remove(0);
    }
}

when(reader.readInt()).thenAnswer(answerFromList);
when(reader.readShort()).thenAnswer(answerFromList);

请注意,此Answer与提供的ReturnElementsOf非常相似,因此您也可以直接使用它。