Java Mockito 笔记

Mockito


1 Overview

2 Maven 项目初始化

3 示例

        3.1 第一个示例

        3.2 自动 Mock

        3.3 Mock 返回值

        3.4 Mock 参数

        3.5 自动注入 Mock 对象

        3.6 验证调用次数

        3.7 预设 Exception

        3.8 Void Mock

        3.9 级联 Mock

        3.10 部分 Mock

4 FAQ

        4.1 注意点

5 References


1 Overview

Mockito 是 Java 中用于 Mock 的一个开源项目。

Mock 用于如下目的

  • 如果依赖的外部系统在测试环境下不可用,使用 Mock 跳过外部系统调用并模拟返回结果
  • 验证是否的确调用了 Mock 对象

2 Maven 项目初始化

  1. 创建 Maven Quick Start 项目

  2. pom.xml 修改如下

    pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    
    	<groupId>com.lld</groupId>
    	<artifactId>test.mockito</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<packaging>jar</packaging>
    
    	<name>test.mockito</name>
    	<url>http://maven.apache.org</url>
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>1.5.4.RELEASE</version>
    	</parent>
    
    	<properties>
    		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    	</properties>
    
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter</artifactId>
    		</dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
    		<dependency>
    			<groupId>org.mockito</groupId>
    			<artifactId>mockito-all</artifactId>
    			<version>1.9.5</version>
    			<scope>test</scope>
    		</dependency>
    	</dependencies>
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    </project>
    

3 示例

3.1 第一个示例

我们先看如下代码

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import java.util.List;

import org.junit.Test;

public class FirstTest {

    @Test
    public void verifyBehavior() {
        @SuppressWarnings("unchecked")
        List<Integer> mock = mock(List.class);
        mock.add(1);
        mock.clear();
        verify(mock).add(1);
        verify(mock).clear();
    }
}

作为第一个示例,我在此详细解释一下

首先,我们使用 Mockito.mock() 方来来生成 Mock 对象。这里我们需要注意两点:

  1. 可以直接生成接口的 Mock 对象
  2. 范型对象 Mock

然后我们调用了 Mock 对象的两个成员方法,在此我们需要更深刻地理解一下 Mock 对象的行为,如果我们在调用 add 方法后打印它的 size,我们会发现结果是 0 而不是 1。也就是说,Mock 对象只是拦截了所有对原始方法的调用并返回对应返回的类型的默认值,而不是真正地实现了这个接口或创建了对象实例。

然后是两个 verify 方法,表示验证 Mock 对象是否调用了对应的方法。注意验证 add 调用时,可以验证输入参数(本例为 1),如果不想验证,只需要确定是否调用,可以使用如下方式验证

import static org.mockito.Matchers.anyInt;
...
verify(mock).add(anyInt());

3.2 自动 Mock

可以使用如下方式自动生成 Mock 对象

import org.junit.Before;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
...
@Mock
private List<Integer> mock;

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
}

3.3 Mock 返回值

下例 mock一个Iterator类,预设当iterator调用next()时第一次返回hello,以后每次都返回world

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.Iterator;
import org.junit.Test;

public class MockReturnTest {

    @Test
    public void verifyBehavior1() {
        @SuppressWarnings("unchecked")
        Iterator<String> iterator = mock(Iterator.class);
        when(iterator.next()).thenReturn("hello").thenReturn("world");
        String result = iterator.next() + " " + iterator.next() + " " + iterator.next();
        assertEquals("hello world world", result);
    }
}

3.4 Mock 参数

可以使用以下 Mockito 对象来模拟任意输入值

import static org.mockito.Matchers.any*

例如 anyString, anyInteger, anyChar 等,也可以使用 any() 方法来生成任意对象,例如

List<String> mock = mock(List.class);
mock.add(any(String.class));

或者使用更简单的 Mockito.any(), 如下所示

import static org.mockito.Mockito.any;

List<String> mock = mock(List.class);
mock.add(any());

3.5 自动注入 Mock 对象

对于如下的情况,我们需要 Mock 某对象的内部方法,如下所示,我们需要 Mock MainServer 内部的 OtherService:

MainService.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MainService {
    @Autowired
    OtherService otherService;
    
    public void call() {
        System.out.println("value is: " + otherService.getValue());
    }
}

OtherService.java

import org.springframework.stereotype.Component;

@Component
public class OtherService {
    public String getValue() {
        return "real value";
    }
}

常规情况下,我们需要手工注入 Mock 对象,如下所示:

AutoInjectTest.java

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DemoConfig.class)
public class AutoInjectTest {
    @Autowired
    MainService service;
    
    @Mock
    OtherService otherService;
    
    @Test
    public void manualInjectTest() {
        otherService = mock(OtherService.class);
        when(otherService.getValue()).thenReturn("mock value");
        service.setOtherService(otherService);
        service.call();
    }
}

PS:需要在 MainService 类中添加 setOtherService() 方法以允许修改 otherService

其中 DemoConfig 是配置类,对于 Spring Boot 框架,Test 类不会自动注入 Autowired 对象,需要使用 Config 类指定加载类,内容如下:

DemoConfig.java

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan({ "com.lld.test" })
public class DemoConfig {

}

但更合理的方式是使用 @InjectMocks 注解来自动注入,如下所示

AutoInjectTest.java

import static org.mockito.Mockito.when;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DemoConfig.class)
public class AutoInjectTest {
    @Autowired
    @InjectMocks
    MainService service;
    
    @Mock
    OtherService otherService;
    
    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        when(otherService.getValue()).thenReturn("mock value");
    }
    
    @Test
    public void autoInjectTest() {
        service.call();
    }
}

注意如下几点:

  • 在 @Before 方法中初始化 Mock 对象及自动注入
  • 在需要自动注入成员的类上添加 @InjectMocks 注解

另外值得注意的是,@InjectMocks 只会注入当前对象的成员,不会递归深度注入对象,例如,我们如果将 MainService 修改如下:

MainService.java

@Component
public class MainService {
    @Autowired
    MiddleService middleService;

    public void callMiddle() {
        System.out.println("value is: " + middleService.getValue());
    }
}

添加 MiddleService 如下所示

MiddleService.java

@Component
public class MiddleService {
    @Autowired
    OtherService otherService;

    public String getValue() {
        return otherService.getValue();
    }
}

这样的话,Unit Test 需要修改如下:

AutoInjectTest.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DemoConfig.class)
public class AutoInjectTest {
    @Autowired
    MainService service;

    @Mock
    OtherService otherService;
    
    @Autowired
    @InjectMocks
    MiddleService middleService;

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        when(otherService.getValue()).thenReturn("mock value");
    }

    @Test
    public void autoInjectDeepTest() {
        service.callMiddle();
    }
}

3.6 验证调用次数

如下代码验证了 add 方法需要被调用两次

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.List;
import org.junit.Test;

public class CallTimesTest {
    @Test
    public void verifyBehavior1() {
        List<Integer> mock = mock(List.class);
        mock.add(1);
        mock.add(1);
        verify(mock, times(2)).add(1);
    }
}

3.7 预设 Exception

下面代码演示了预设 Exceptio 发生

import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import java.io.IOException;
import java.io.OutputStream;
import org.junit.Test;

public class ExceptionTest {
    @Test(expected = IOException.class)
    public void when_thenThrow() throws IOException {
        OutputStream outputStream = mock(OutputStream.class);
        doThrow(new IOException()).when(outputStream).close();
        outputStream.close();
    }
}

代码说明如下:

  • @Test(expected = IOException.class) 表示该测试需要有 IOException 抛出
  • doThrow 表示指定操作将抛出指定异常

3.8 Void Mock

前面的 thenReturn 只适用于有返回值的方法,本例讲述如何 Mock void 方法

声明服务类如下

package com.lld.test.mockito;

import org.springframework.stereotype.Component;

@Component
public class VoidService {
    public void sayHi(String name) {
        System.out.println("Hello, " + name);
    }
}

测试类如下所示

package com.lld.test.mockito;

import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = DemoConfig.class)
public class VoidTest {
    @Mock
    VoidService voidService;

    @Before
    public void setup() throws Exception {
        MockitoAnnotations.initMocks(this);
    }
    
    @Test
    public void voidTest() {
        doNothing().when(voidService).sayHi(anyString());
        voidService.sayHi("Lindong");
        verify(voidService, times(1)).sayHi(anyString());
    }
    
    @Test
    public void voidArgumentTest() {
        ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class);
        doNothing().when(voidService).sayHi(argumentCaptor.capture());
        voidService.sayHi("Lindong");
        assertEquals("Lindong", argumentCaptor.getValue());        
    }

    @Test
    public void answerTest() {
        doAnswer(answer -> {
            String name = answer.getArgumentAt(0, String.class);
            System.out.println("invoke VoidService with argument: " + name);
            return null;
        }).when(voidService).sayHi(anyString());
        voidService.sayHi("Lindong");
    }
}

代码说明如下

  • voidTest 演示了如何简单地 Mock void 方法
  • voidArgumentTest 演示了如何获取 void 方法的参数
  • answerTest 演示了如何截获 void 调用

3.9 级联 Mock

本例演示了如何自动 Mock 所有对象下的子对象

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import org.junit.Test;

public class DeepMockTest {
    @Test
    public void deepstubsAutoTest() {
        Account account = mock(Account.class, RETURNS_DEEP_STUBS);
        when(account.getRailwayTicket().getDestination()).thenReturn("Beijing");
        account.getRailwayTicket().getDestination();
        verify(account.getRailwayTicket()).getDestination();
        assertEquals("Beijing", account.getRailwayTicket().getDestination());
    }

    @Test
    public void deepstubsManualTest() {
        Account account = mock(Account.class);
        RailwayTicket railwayTicket = mock(RailwayTicket.class);
        when(account.getRailwayTicket()).thenReturn(railwayTicket);
        when(railwayTicket.getDestination()).thenReturn("Beijing");

        account.getRailwayTicket().getDestination();
        verify(account.getRailwayTicket()).getDestination();
        assertEquals("Beijing", account.getRailwayTicket().getDestination());
    }

    public class RailwayTicket {
        private String destination;

        public String getDestination() {
            return destination;
        }

        public void setDestination(String destination) {
            this.destination = destination;
        }
    }

    public class Account {
        private RailwayTicket railwayTicket;

        public RailwayTicket getRailwayTicket() {
            return railwayTicket;
        }

        public void setRailwayTicket(RailwayTicket railwayTicket) {
            this.railwayTicket = railwayTicket;
        }
    }
}

代码说明如下:

  • deepstubsAutoTest 演示了自动创建子对象的 Mock (推荐)
  • deepstubsManualTest 演示了手动创建子对象的 Mock

3.10 部分 Mock

如下例所示,我们需要使用 Mock 跳过 Exception

import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;

import org.junit.Test;

public class PartialMockTest {

    @Test
    public void partialMockTest() throws Exception {
        TestObj mockObj = spy(new TestObj());
        doNothing().when(mockObj).m1();
        mockObj.m3();
    }
    
    class TestObj {
        public void m1() throws Exception {
            throw new Exception("exception");
        }
        
        public void m2() {
            System.out.println("m2 is invoked");
        }
        
        public void m3() throws Exception {
            m1();
            m2();
        }
    }
}

我们使用了 spy 方法,它返回的对象是一个真实的对象,所有的方法调用也都是真的方法调用。但像例子中演示的,可以 Mock 掉指定的方法。如果有返回值,也可以和以前的例子一样使用 thenReturn。

4 FAQ

4.1 注意点

  1. 对于 @Mock 标注,MockitoAnnotations.initMocks(this); 一定要放在第一行

  2. Mock 对象的所有方法均为假方法,而不是默认实现

5 References

Mockito 1.x

Mockito 2.x

How to mock with Mockito

posted @ 2019-05-23 12:55  真栋哥  阅读(2099)  评论(0编辑  收藏  举报