Chapter 3: 使用Mockito

    举例来说,比如你需要测试,而这个方法依赖了Bar.methodB,又传递依赖到了Zoo.methodC,于是它们的依赖关系就是Foo->Bar->Zoo,所以在测试代码里你必须自行new Bar和Zoo。

    有人会说:”我直接用Spring的DI机制不就行了吗?”的确,你可以用Spring的DI机制,不过解决不了测试代码耦合度过高的问题:

    因为Foo方法内部调用了Bar和Zoo的方法,所以你对其做单元测试的时候,必须完全了解Bar和Zoo方法的内部逻辑,并且谨慎的传参和assert结果,一旦Bar和Zoo的代码修改了,你的Foo测试代码很可能就会运行失败。

    所以这个时候我们需要一种机制,能过让我们在测试Foo的时候不依赖于Bar和Zoo的具体实现,即不关心其内部逻辑,只关注Foo内部的逻辑,从而将Foo的每个逻辑分支都测试到。

    所以业界就产生了Mock技术,它可以让我们做一个假的Bar(不需要Zoo,因为只有真的Bar才需要Zoo),然后控制这个假的Bar的行为(让它返回什么就返回什么),以此来测试Foo的每个逻辑分支。

    你肯定会问,这样的测试有意义吗?在真实环境里Foo用的是真的Bar而不是假的Bar,你用假的Bar测试成功能代表真实环境不出问题?

    Mock技术的另一个好处是能够让你尽量避免集成测试,比如我们可以Mock一个Repository(数据库操作类),让我们尽量多写单元测试,提高测试代码执行效率。

    spring-boot-starter-test依赖了,所以我们会在本章里使用Mockito来讲解。

    先介绍一下接下来要被我们测试的类Foo、俩兄弟。

    例子1: 不使用Mock技术

    源代码NoMockTest

    1. public class NoMockTest {
    2. @Test
    3. public void testCheckCodeDuplicate1() throws Exception {
    4. FooImpl foo = new FooImpl();
    5. foo.setBar(new Bar() {
    6. @Override
    7. public Set<String> getAllCodes() {
    8. return Collections.singleton("123");
    9. }
    10. });
    11. assertEquals(foo.checkCodeDuplicate("123"), true);
    12. }
    13. @Test
    14. public void testCheckCodeDuplicate2() throws Exception {
    15. FooImpl foo = new FooImpl();
    16. foo.setBar(new FakeBar(Collections.singleton("123")));
    17. assertEquals(foo.checkCodeDuplicate("123"), true);
    18. public class FakeBar implements Bar {
    19. private final Set<String> codes;
    20. public FakeBar(Set<String> codes) {
    21. this.codes = codes;
    22. }
    23. @Override
    24. public Set<String> getAllCodes() {
    25. return codes;
    26. }
    27. }
    28. }

    这个测试代码里用到了两种方法来做假的Bar:

    1. 匿名内部类
    2. 做了一个FakeBar

    这两种方式都不是很优雅,看下面使用Mockito的例子。

    1. 我们先给了一个Bar的Mock实现: private Bar bar;
    2. 然后又规定了getAllCodes方法的返回值:when(bar.getAllCodes()).thenReturn(Collections.singleton("123"))。这样就把一个假的Bar定义好了。
    3. 最后利用Mockito把Bar注入到Foo里面,@InjectMocks private FooImpl foo;MockitoAnnotations.initMocks(this);

    例子3:配合Spring Test

    源代码:

    1. @ContextConfiguration(classes = FooImpl.class)
    2. @TestExecutionListeners(listeners = MockitoTestExecutionListener.class)
    3. public class Spring_1_Test extends AbstractTestNGSpringContextTests {
    4. @MockBean
    5. private Bar bar;
    6. @Autowired
    7. @Test
    8. public void testCheckCodeDuplicate() throws Exception {
    9. when(bar.getAllCodes()).thenReturn(Collections.singleton("123"));
    10. }
    11. }

    要注意,如果要启用Spring和Mockito,必须添加这么一行:@TestExecutionListeners(listeners = MockitoTestExecutionListener.class)

    当Bean存在这种依赖关系当时候:LooImpl -> FooImpl -> Bar,我们应该怎么测试呢?

    按照Mock测试的原则,这个时候我们应该mock一个Foo对象,把这个注入到LooImpl对象里,就像例子3里的一样。

    不过如果你不想mock Foo而是想mock Bar的时候,其实做法和前面也差不多,Spring会自动将mock Bar注入到FooImpl中,然后将FooImpl注入到LooImpl中。

    源代码:

    例子5:配合Spring Boot Test

    源代码Boot_1_Test

    1. @SpringBoot_1_Test(classes = { FooImpl.class })
    2. @TestExecutionListeners(listeners = MockitoTestExecutionListener.class)
    3. public class Boot_1_Test extends AbstractTestNGSpringContextTests {
    4. @MockBean
    5. private Bar bar;
    6. @Autowired
    7. private Foo foo;
    8. @Test
    9. public void testCheckCodeDuplicate() throws Exception {
    10. when(bar.getAllCodes()).thenReturn(Collections.singleton("123"));
    11. assertEquals(foo.checkCodeDuplicate("123"), true);
    12. }
    13. }