Mockito

2022-01-12/2022-01-12
0 评论 21 浏览

参考文档:

Mockito教程
【java】单元测试Mockito中的Mock和Spy
@InjectMocks
Mockito Verify常见用法

1. Mockito 介绍

1.1 Mockito 是什么

Mockito 是 mocking 框架,它让你用简洁的 API 做测试。

1.2 为什么需要 Mock

测试驱动的开发( TDD)要求我们先写单元测试,再写实现代码。在写单元测试的过程中,我们往往会遇到要测试的类有很多依赖,这些依赖的类/对象/资源又有别的依赖,从而形成一个大的依赖树,要在单元测试的环境中完整地构建这样的依赖,是一件很困难的事情。如下图所示:

484791-20170120134044703-96948251

为了测试类 A,我们需要 Mock B 类和 C 类(用虚拟对象来代替)如下图所示:

484791-20170120134311703-125872357

Mock 是类的实例,是一个虚拟对象,并不是实际对象,不太好理解,看下面例子。

下面例子基于引用

1import static org.mockito.Mockito.*;
2import static org.junit.jupiter.api.Assertions.*;

2. 实例

2.1 验证行为

 1@Test
 2public void verify_behaviour(){
 3	//模拟创建一个List对象	
 4	List mock = mock(List.class);
 5	//使用mock的对象
 6	mock.add(1);
 7	mock.clear();
 8	// 验证add(1)和clear()行为是否发生
 9	verify(mock).add(1);
10	verify(mock).clear();
11}

2.2 模拟我们所期望的值

 1@Test
 2public void when_thenReturn(){
 3    // mock一个Iterator类
 4    Iterator iterator = mock(Iterator.class);
 5    // 预设当iterator调用next()时第一次返回hello,第n次都返回world
 6    when(iterator.next()).thenReturn("hello").thenReturn("world");
 7    // 使用mock的对象
 8    String result = iterator.next() + " " + iterator.next() + " " + iterator.next();
 9    // 验证结果
10    assertEquals("hello world world",result);
11}
 1@Test
 2public void with_arguments(){
 3    Comparable comparable = mock(Comparable.class);
 4    //预设根据不同的参数返回不同的结果
 5    when(comparable.compareTo("Test")).thenReturn(1);
 6    when(comparable.compareTo("Omg")).thenReturn(2);
 7    assertEquals(1, comparable.compareTo("Test"));
 8    assertEquals(2, comparable.compareTo("Omg"));
 9    //对于没有预设的情况会返回默认值
10    assertEquals(0, comparable.compareTo("Not stub"));
11}
 1@Test
 2public void with_unspecified_arguments(){
 3    List list = mock(List.class);
 4    //匹配任意参数
 5    when(list.get(anyInt())).thenReturn(1);
 6    when(list.contains(argThat(new IsValid()))).thenReturn(true);
 7    assertEquals(1, list.get(1));
 8    assertEquals(1, list.get(999));
 9    assertTrue(list.contains(1));
10    assertTrue(!list.contains(3));
11}

2.3 @Mock

在上面的测试中我们在每个测试方法里都mock了一个List对象,为了避免重复的mock,是测试类更具有可读性,我们可以使用下面的注解方式来快速模拟对象:

1@Mock
2private List mockList;

运行这个测试类你会发现报错了,mock的对象为NULL,为此我们必须在基类中添加初始化mock的代码

 1public class MockitoExample2 {
 2    @Mock
 3    private List mockList;
 4
 5    public MockitoExample2(){
 6        MockitoAnnotations.initMocks(this);
 7    }
 8
 9    @Test
10    public void shorthand(){
11        mockList.add(1);
12        verify(mockList).add(1);
13    }
14}

或者使用 MockitoExtension

 1@ExtendWith(MockitoExtension.class)
 2public class MockitoExample2 {
 3    @Mock
 4    private List mockList;
 5
 6    public MockitoExample2(){
 7        MockitoAnnotations.initMocks(this);
 8    }
 9
10    @Test
11    public void shorthand(){
12        mockList.add(1);
13        verify(mockList).add(1);
14    }
15}

2.4 @Spy

  • Mock不是真实的对象,它只是用类型的class创建了一个虚拟对象,并可以设置对象行为
  • Spy是一个真实的对象,但它可以设置对象行为
  • 设置 spy 逻辑时不能再使用 when(某对象.某方法).thenReturn(某对象) 的语法,而是需要使用 doReturn(某对象).when(某对象).某方法 或者 doNothing(某对象).when(某对象).某方法
 1public class Main {
 2 
 3    public void fun(String s) {
 4        System.out.println(s + " : fun");
 5        fun1(s);
 6        fun2(s);
 7    }
 8 
 9    public void fun1(String s) {
10        System.out.println(s + " : fun1");
11    }
12 
13    private void fun2(String s) {
14        System.out.println(s + " : fun2");
15    }
16 
17    public int getVal(){
18        return 5;
19    }
20}

mock 使用实例

  1. 使用 doCallRealMethod().when() 调用函数真正部分。
  2. 使用 when().thenReturn 自定义函数返回值。
 1@ExtendWith(MockitoExtension.class)
 2 public class MainTest {
 3 
 4    @Mock
 5    Main mockMain;
 6 
 7    @Test
 8    public void testFun() {
 9        // 执行mock,而不是真正部分,所以没有打印任何信息
10        mockMain.fun("mock test One");
11 
12        // doCallRealMethod声明后,执行真正部分
13        // 但是Mock只能对public(fun1)和protected函数进行mock
14        // 对private函数(fun2)仍执行真正部分
15        // 所以输出fun和fun2
16        doCallRealMethod().when(mockMain).fun(anyString());
17        mockMain.fun("mock test Two");
18 
19        // 执行mock,输出int的默认值0,而不是5
20        System.out.println("val: " + mockMain.getVal());
21        // when声明后,既不走真正部分,也不走mock,直接返回thenReturn()中定义的值
22        // 注意:该值的类型需要和when中函数返回值类型一致
23        when(mockMain.getVal()).thenReturn(10);
24        System.out.println("val: " + mockMain.getVal());
25    }
26 
27}

20180729194933914

Spy 使用实例

使用 when().thenReturn 自定义函数返回值。

 1@ExtendWith(MockitoExtension.class)
 2public class MainTest {
 3 
 4    @Spy
 5    Main spyMain;
 6 
 7    @Test
 8    public void testFun() {
 9        // 执行真正部分
10        spyMain.fun("mock test One");
11 
12        // 执行真正部分
13        System.out.println("val: " + spyMain.getVal());
14        // 自定义返回值
15        when(spyMain.getVal()).thenReturn(10);
16        System.out.println("val: " + spyMain.getVal());
17    }
18}

20180729194948236

2.5 @InjectMocks

  • @InjectMocks:创建一个实例,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。

  • spring 使用 @Autowird 等方式完成自动注入。在单元测试中,没有启动 spring 框架,@Autowird 无法自动注入,此时就需要通过 @InjectMocks完成依赖注入。

  • @InjectMocks 是无法注入其他 @InjectMocks 字段的,比如:

     1@ExtendWith(MockitoExtension.class)
     2public class MyControllerTest {
     3
     4    @Mock
     5    private MyRepository myRepository;
     6
     7    @InjectMocks
     8    private MyService myService;
     9
    10    @InjectMocks
    11    private MyController myController;
    12
    13    @Test
    14    public void doSomething() throws Exception {
    15        this.myController.doSomething();
    16    }
    17}
    

2.6 @ExtendWith(SpringExtension.class)

当涉及Spring时

如果您想在测试中使用 Spring 测试框架功能(例如)@MockBean,则必须使用 @ExtendWith(SpringExtension.class)。它取代了不推荐使用的 JUnit4 @RunWith(SpringJUnit4ClassRunner.class)。

引入后,在 springboot 项目中,依旧无法使用 @Autowired, 无法加载 bean,只是配置了容器。

当不涉及Spring时

例如,如果您只想涉及Mockito而不必涉及Spring,那么当您只想使用 @Mock/ @InjectMocks 批注时,您就想使用 @ExtendWith(MockitoExtension.class),因为它不会加载到很多不需要的 Spring 东西中。它替换了不推荐使用的 JUnit4 @RunWith(MockitoJUnitRunner.class)。

2.7 @MockBean

我们可以使用 @mockBean 注解将 Mock 对象添加到 Spring 上下文中。

Mock 将替换 Spring 上下文中任何相同类型的现有 bean,如果没有定义相同类型的 bean,将添加一个新的 bean。

2.8 @SpyBean

注入真实对象,受 spring 管理,相当于自动替换对应类型 bean 的注入,比如 @Autowired 注入。

@SpyBean 解决了 SpringBoot 的单元测试中 @MockBean 不能 mock 库中自动装配的 Bean 的局限。使 SpringBoot 的单元测试更灵活也更简单。

 1@ExtendWith(SpringExtension.class)
 2public class MyTest {
 3
 4    @MockBean
 5    private MyMapper mapper;
 6
 7    @SpyBean
 8    private MyService myService;
 9
10    @Test
11    void test() {
12        System.out.println(myService.getUsername());
13    }
14
15}
评论
发表评论
       
       
取消