Java 安装 Java:第一个程序 Hello World Java:建议使用 UTF-8 编写 Java 代码 Java:package 包命名规范 使用 Intellij IDEA 创建 Java 项目 Java 布尔类型 Java 处理日期和时间 Java 正则表达式 Java finalize 方法 Java:空值 null Java 如何触发垃圾回收 Java ThreadLocal Java InheritableThreadLocal Java Integer之间的比较 Java 动态代理 Java 匿名类 Java 枚举 Java 如何静态导入 import static println Java 引用级别:强引用、软引用、弱引用、幽灵引用 Java try finally return 解惑 Java WeakHashMap Java ReferenceQueue 怎么写 Java 示例代码? Java 匿名类双大括号初始化 什么是 Java Bean Java 多行字符串 Java 快速生成 List Java 快速生成 Map Java 将异常堆栈转换为 String JDK SPI 的使用和源码分析 Java Map 中的 key 和 value 能否为 null ? Java List 和 数组的互相转换 Java 获取环境变量 Java 获取和设置系统属性 Java:如何获取当前进程的 PID ? Java 字符串左侧 右侧补充空格或者其他字符 Java 线程 Java:如何获取文本文件内容 Java:读取资源文件内容 Java:使用 JavaFx 构建 GUI Java:Class 类 Java:使用 instanceof 判断对象类型 一个自定义的 Java 工具类 Java:获取当前函数所属类的类名 Java:获取当前执行的函数名 Java:使用 String 的 split 函数拆分字符串 Java:获取字符的 Unicode 编号(代码点) Java:获取当前工作目录 Java:使用 Class 对象的 isArray 方法判断对象是否为数组 使用 Java 生成 CSV 文件 Java Mockito 测试框架快速入门 JUnit 入门 JUnit 单测隔离 Java JOOR 反射库 Java alibaba transmittable-thread-local 库:让 ThreadLocal 跨线程传播 Java 日志组件 slf4j 的使用和源码分析 Java Lombok 库:为你减少样板代码 Java:使用 cglib 实现动态代理 Java Hibernate validator 校验框架 Java 使用 Hessian2 序列化和反序列化 H2 数据库快速入门 Java:使用 Gson 库处理 JSON 数据 Java 集成 groovy 构建规则引擎 Java 13:安装 Java 13 新特性:文本块(多行字符串) 卸载 MacOS 上安装的 Java Java:执行 sql 文件 Java JDK 有哪些发行版 ? java拾遗:String和数组 java拾遗:由反转数组想到System.out的实现机制 java拾遗:如何读取properties文件内容 Java并发概念汇总 java拾遗:System.out.println()是什么? java拾遗:通过示例理解位运算 使用“庖丁解牛”进行中文分词 DBUtils简明教程 试用velocity模板引擎 Java:将字符串哈希为数字 kafka SnappyError no native library is found 问题

Java Mockito 测试框架快速入门


#Java


Mockito 是一个模拟测试框架。主要功能是模拟类/对象的行为。

入门

Mockito 是一个模拟测试框架。主要功能是模拟类/对象的行为。

Mockito 一般用于控制调用外部的返回值,让我们只关心和测试自己的业务逻辑。

我们看一个示例:

package demo;

import java.util.Random;

public class HttpService {

    public int queryStatus() {
        // 发起网络请求,提取返回结果
        // 这里用随机数模拟结果
        return new Random().nextInt(2);
    }

}
package demo;

public class ExampleService {

    private HttpService httpService;

    public void setHttpService(HttpService httpService) {
        this.httpService = httpService;
    }

    public String hello() {
        int status = httpService.queryStatus();
        if (status == 0) {
            return "你好";
        }
        else if (status == 1) {
            return "Hello";
        }
        else {
            return "未知状态";
        }
    }

}

使用示例:

package demo;

public class Main {

    public static void main(String[] args) {
        HttpService realHttpService = new HttpService();
        ExampleService exampleService = new ExampleService();
        exampleService.setHttpService(realHttpService);
        System.out.println( exampleService.hello() );
    }
}

每次运行的结果可能不同,可能是你好,也可能是Hello

现在我们引入 mockito。如果是用 gradle 构建 gradle 项目,加入以下依赖:

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    testCompile group: 'org.mockito', name: 'mockito-core', version: '2.25.1'
}

注意,我们引入了 junit 来编写断言。断言是测试的核心。

整个项目结构如下:

├── build.gradle
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── HttpService.java
    │   │       ├── Main.java
    │   │       └── ExampleService.java
    │   └── resources
    └── test
        ├── java
        │   └── demo
        │       └── ExampleServiceTest.java
        └── resources

其中 test 目录下的 ExampleServiceTest 类,是我们新增的测试类:

package demo;

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


public class ExampleServiceTest {

    @Test
    public void test() {
        // 创建mock对象
        HttpService mockHttpService = mock(HttpService.class);
        // 使用 mockito 对 queryStatus 方法打桩
        when(mockHttpService.queryStatus()).thenReturn(1);
        // 调用 mock 对象的 queryStatus 方法,结果永远是 1
        Assert.assertEquals(1, mockHttpService.queryStatus());

        ExampleService exampleService = new ExampleService();
        exampleService.setHttpService(mockHttpService);
        Assert.assertEquals("Hello", exampleService.hello() );
    }

}

我们通过 mock 函数生成了一个 HttpService 的 mock 对象(这个对象是动态生成的)。

通过 when .. thenReturn 指定了当调用 mock对象的 queryStatus 方法时,返回 1 ,这个叫做打桩

然后将 mock 对象注入到 exampleService 中,exampleService.hello() 的返回永远是 Hello

引入 Mockito 依赖包

如果是用 gradle 构建 gradle 项目,加入以下依赖:

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    testCompile group: 'org.mockito', name: 'mockito-core', version: '2.25.1'
}

注意,这里额外引入了 junit 来编写断言。断言是测试的核心。

使用 mock 方法模拟类和接口

org.mockito.Mockito 的 mock 方法可以模拟类和接口。

mock 类:

import org.junit.Assert;
import org.junit.Test;
import java.util.Random;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Test
    public void test() {
        Random mockRandom = mock(Random.class);
        when(mockRandom.nextInt()).thenReturn(100);  // 指定调用 nextInt 方法时,永远返回 100

        Assert.assertEquals(100, mockRandom.nextInt());
        Assert.assertEquals(100, mockRandom.nextInt());
    }
}

注意,mock 对象的方法的返回值默认都是返回类型的默认值。例如,返回类型是 int,默认返回值是 0;返回类型是一个类,默认返回值是 null。

import org.junit.Assert;
import org.junit.Test;
import java.util.Random;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Test
    public void test() {
        Random mockRandom = mock(Random.class);

        System.out.println( mockRandom.nextBoolean() );
        System.out.println( mockRandom.nextInt() );
        System.out.println( mockRandom.nextDouble() );
    }
}

运行 test 方法,输出:

false
0
0.0

mock 接口:

import org.junit.Assert;
import org.junit.Test;

import java.util.List;

import static org.mockito.Mockito.*;

public class MockitoDemo {


    @Test
    public void test() {
        List mockList = mock(List.class);

        Assert.assertEquals(0, mockList.size());
        Assert.assertEquals(null, mockList.get(0));

        mockList.add("a");  // 调用 mock 对象的写方法,是没有效果的

        Assert.assertEquals(0, mockList.size());      // 没有指定 size() 方法返回值,这里结果是默认值
        Assert.assertEquals(null, mockList.get(0));   // 没有指定 get(0) 返回值,这里结果是默认值

        when(mockList.get(0)).thenReturn("a");          // 指定 get(0)时返回 a

        Assert.assertEquals(0, mockList.size());        // 没有指定 size() 方法返回值,这里结果是默认值
        Assert.assertEquals("a", mockList.get(0));      // 因为上面指定了 get(0) 返回 a,所以这里会返回 a

        Assert.assertEquals(null, mockList.get(1));     // 没有指定 get(1) 返回值,这里结果是默认值
    }
}

@Mock 注解

@Mock 注解可以理解为对 mock 方法的一个替代。

使用该注解时,要使用MockitoAnnotations.initMocks 方法,让注解生效。

示例1:

import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Random;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Mock
    private Random random;

    @Test
    public void test() {
        // 让注解生效
        MockitoAnnotations.initMocks(this);

        when(random.nextInt()).thenReturn(100);

        Assert.assertEquals(100, random.nextInt());
    }

}

MockitoAnnotations.initMocks 放在 junit 的 @Before 注解修饰的函数中更合适。

示例2:

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Random;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Mock
    private Random random;

    @Before
    public void before() {
        // 让注解生效
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void test() {
        when(random.nextInt()).thenReturn(100);

        Assert.assertEquals(100, random.nextInt());
    }

}

MockitoAnnotations.initMocks 的一个替代方案是使用 MockitoJUnitRunner 。

示例3:

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.Random;

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {

    @Mock
    private Random random;

    @Test
    public void test() {
        when(random.nextInt()).thenReturn(100);
        Assert.assertEquals(100, random.nextInt());
    }

}

mock 泛型类、泛型接口

示例:

import org.junit.Assert;
import org.junit.Test;

import java.util.ArrayList;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Test
    public void test() {
        ArrayList<String> mockList = mock(ArrayList.class);  // 这种写法不够精确,IDE也会警告

        when(mockList.get(0)).thenReturn("abc");

        Assert.assertEquals(3, mockList.get(0).length());
    }

}

下面这种用 @Mock 注解的方法,IDE 不会警告:

import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Mock
    private ArrayList<String> mockList;

    @Test
    public void test() {

        MockitoAnnotations.initMocks(this);

        when(mockList.get(0)).thenReturn("abc");

        Assert.assertEquals(3, mockList.get(0).length());
    }

}

参数匹配

精确匹配

我们之前介绍过这样的例子:

import org.junit.Assert;
import org.junit.Test;

import java.util.List;

import static org.mockito.Mockito.*;

public class MockitoDemo {


    @Test
    public void test() {
        List mockList = mock(List.class);

        Assert.assertEquals(0, mockList.size());
        Assert.assertEquals(null, mockList.get(0));

        mockList.add("a");  // 调用 mock 对象的写方法,是没有效果的

        Assert.assertEquals(0, mockList.size());      // 没有指定 size() 方法返回值,这里结果是默认值
        Assert.assertEquals(null, mockList.get(0));   // 没有指定 get(0) 返回值,这里结果是默认值

        when(mockList.get(0)).thenReturn("a");          // 指定 get(0)时返回 a

        Assert.assertEquals(0, mockList.size());        // 没有指定 size() 方法返回值,这里结果是默认值
        Assert.assertEquals("a", mockList.get(0));      // 因为上面指定了 get(0) 返回 a,所以这里会返回 a

        Assert.assertEquals(null, mockList.get(1));     // 没有指定 get(1) 返回值,这里结果是默认值
    }
}

其中when(mockList.get(0)).thenReturn("a"); 指定了get(0)的返回值,这个 0 就是参数的精确匹配。我们还可以让不同的参数对应不同的返回值,例如:

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.List;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Mock
    private List<String> mockStringList;

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

    @Test
    public void test() {

        mockStringList.add("a");

        when(mockStringList.get(0)).thenReturn("a");
        when(mockStringList.get(1)).thenReturn("b");

        Assert.assertEquals("a", mockStringList.get(0));
        Assert.assertEquals("b", mockStringList.get(1));

    }

}

对于精确匹配,还可以用 eq,例如:

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.List;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Mock
    private List<String> mockStringList;

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

    @Test
    public void test() {

        mockStringList.add("a");

        when(mockStringList.get(eq(0))).thenReturn("a");  // 虽然可以用eq进行精确匹配,但是有点多余
        when(mockStringList.get(eq(1))).thenReturn("b");

        Assert.assertEquals("a", mockStringList.get(0));
        Assert.assertEquals("b", mockStringList.get(1));

    }

}

模糊匹配

可以使用 Mockito.anyInt() 匹配所有类型为 int 的参数:

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.List;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Mock
    private List<String> mockStringList;

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

    @Test
    public void test() {

        mockStringList.add("a");

        when(mockStringList.get(anyInt())).thenReturn("a");  // 使用 Mockito.anyInt() 匹配所有的 int

        Assert.assertEquals("a", mockStringList.get(0)); 
        Assert.assertEquals("a", mockStringList.get(1));

    }

}

anyInt 只是用来匹配参数的工具之一,目前 mockito 有多种匹配函数,部分如下:

函数名 匹配类型
any() 所有对象类型
anyInt() 基本类型 int、非 null 的 Integer 类型
anyChar() 基本类型 char、非 null 的 Character 类型
anyShort() 基本类型 short、非 null 的 Short 类型
anyBoolean() 基本类型 boolean、非 null 的 Boolean 类型
anyDouble() 基本类型 double、非 null 的 Double 类型
anyFloat() 基本类型 float、非 null 的 Float 类型
anyLong() 基本类型 long、非 null 的 Long 类型
anyByte() 基本类型 byte、非 null 的 Byte 类型
anyString() String 类型(不能是 null)
anyList() List<T> 类型(不能是 null)
anyMap() Map<K, V>类型(不能是 null)
anyCollection() Collection<T>类型(不能是 null)
anySet() Set<T>类型(不能是 null)
any(Class<T> type) type类型的对象(不能是 null)
isNull() null
notNull() 非 null
isNotNull() 非 null

参数匹配顺序

如果参数匹配即生命了精确匹配,也声明了模糊匹配;又或者同一个值的精确匹配出现了两次,使用时会匹配哪一个?

会匹配符合匹配条件的最新声明的匹配。

示例:

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.util.List;

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {


    @Mock
    private List<String> testList;

    @Test
    public void test01() {

        // 精确匹配 0
        when(testList.get(0)).thenReturn("a");

        Assert.assertEquals("a", testList.get(0));

        // 精确匹配 0
        when(testList.get(0)).thenReturn("b");

        Assert.assertEquals("b", testList.get(0));

        // 模糊匹配
        when(testList.get(anyInt())).thenReturn("c");

        Assert.assertEquals("c", testList.get(0));
        Assert.assertEquals("c", testList.get(1));

    }

    @Test
    public void test02() {

        // 模糊匹配
        when(testList.get(anyInt())).thenReturn("c");

        Assert.assertEquals("c", testList.get(0));
        Assert.assertEquals("c", testList.get(1));

        // 精确匹配 0
        when(testList.get(0)).thenReturn("a");

        Assert.assertEquals("a", testList.get(0));
        Assert.assertEquals("c", testList.get(1));

    }

}

spy 和 @Spy 注解

spy 和 mock不同,不同点是:

  • spy 的参数是对象示例,mock 的参数是 class。
  • 被 spy 的对象,调用其方法时默认会走真实方法。mock 对象不会。

下面是一个对比:

import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;


class ExampleService {

    int add(int a, int b) {
        return a+b;
    }

}

public class MockitoDemo {

    // 测试 spy
    @Test
    public void test_spy() {

        ExampleService spyExampleService = spy(new ExampleService());

        // 默认会走真实方法
        Assert.assertEquals(3, spyExampleService.add(1, 2));

        // 打桩后,不会走了
        when(spyExampleService.add(1, 2)).thenReturn(10);
        Assert.assertEquals(10, spyExampleService.add(1, 2));

        // 但是参数比匹配的调用,依然走真实方法
        Assert.assertEquals(3, spyExampleService.add(2, 1));

    }

    // 测试 mock
    @Test
    public void test_mock() {

        ExampleService mockExampleService = mock(ExampleService.class);

        // 默认返回结果是返回类型int的默认值
        Assert.assertEquals(0, mockExampleService.add(1, 2));

    }


}

spy 对应注解 @Spy,和 @Mock 是一样用的。

import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

import static org.mockito.Mockito.*;


class ExampleService {

    int add(int a, int b) {
        return a+b;
    }

}

public class MockitoDemo {

    @Spy
    private ExampleService spyExampleService;

    @Test
    public void test_spy() {

        MockitoAnnotations.initMocks(this);

        Assert.assertEquals(3, spyExampleService.add(1, 2));

        when(spyExampleService.add(1, 2)).thenReturn(10);
        Assert.assertEquals(10, spyExampleService.add(1, 2));

    }

}

对于@Spy,如果发现修饰的变量是 null,会自动调用类的无参构造函数来初始化。

所以下面两种写法是等价的:

// 写法1
@Spy
private ExampleService spyExampleService;

// 写法2
@Spy
private ExampleService spyExampleService = new ExampleService();

如果没有无参构造函数,必须使用写法2。例子:

import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;

class ExampleService {

    private int a;

    public ExampleService(int a) {
        this.a = a;
    }

    int add(int b) {
        return a+b;
    }

}

public class MockitoDemo {

    @Spy
    private ExampleService spyExampleService = new ExampleService(1);

    @Test
    public void test_spy() {

        MockitoAnnotations.initMocks(this);

        Assert.assertEquals(3, spyExampleService.add(2));

    }

}

使用 @InjectMocks 注解注入 mock 对象

mockito 会将 @Mock@Spy 修饰的对象自动注入到 @InjectMocks 修饰的对象中。

注入方式有多种,mockito 会按照下面的顺序尝试注入:

  1. 构造函数注入
  2. 设值函数注入(set函数)
  3. 属性注入

示例:

准备两个业务类:

package demo;

import java.util.Random;

public class HttpService {

    public int queryStatus() {
        // 发起网络请求,提取返回结果
        // 这里用随机数模拟结果
        return new Random().nextInt(2);
    }

}
package demo;

public class ExampleService {

    private HttpService httpService;

    public String hello() {
        int status = httpService.queryStatus();
        if (status == 0) {
            return "你好";
        }
        else if (status == 1) {
            return "Hello";
        }
        else {
            return "未知状态";
        }
    }

}

编写测试类:

import org.junit.Assert;
import org.junit.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.when;


public class ExampleServiceTest {

    @Mock
    private HttpService httpService;

    @InjectMocks
    private ExampleService exampleService = new ExampleService(); // 会将 httpService 注入进去

    @Test
    public void test01() {

        MockitoAnnotations.initMocks(this);

        when(httpService.queryStatus()).thenReturn(0);

        Assert.assertEquals("你好", exampleService.hello());

    }

}

使用 thenReturn 设置方法的返回值

thenReturn 用来指定特定函数和参数调用的返回值。

比如:

import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;

import java.util.Random;

public class MockitoDemo {

    @Test
    public void test() {

        Random mockRandom = mock(Random.class);

        when(mockRandom.nextInt()).thenReturn(1);

        Assert.assertEquals(1, mockRandom.nextInt());

    }

}

thenReturn 中可以指定多个返回值。在调用时返回值依次出现。若调用次数超过返回值的数量,再次调用时返回最后一个返回值。

import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;

import java.util.Random;

public class MockitoDemo {
    
    @Test
    public void test() {
        Random mockRandom = mock(Random.class);

        when(mockRandom.nextInt()).thenReturn(1, 2, 3);

        Assert.assertEquals(1, mockRandom.nextInt());
        Assert.assertEquals(2, mockRandom.nextInt());
        Assert.assertEquals(3, mockRandom.nextInt());
        Assert.assertEquals(3, mockRandom.nextInt());
        Assert.assertEquals(3, mockRandom.nextInt());
    }

}

使用 thenThrow 让方法抛出异常

thenThrow 用来让函数调用抛出异常。

import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;

import java.util.Random;

public class MockitoDemo {

    @Test
    public void test() {

        Random mockRandom = mock(Random.class);

        when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常"));

        try {
            mockRandom.nextInt();
            Assert.fail();  // 上面会抛出异常,所以不会走到这里
        } catch (Exception ex) {
            Assert.assertTrue(ex instanceof RuntimeException);
            Assert.assertEquals("异常", ex.getMessage());
        }

    }

}

thenThrow 中可以指定多个异常。在调用时异常依次出现。若调用次数超过异常的数量,再次调用时抛出最后一个异常。

import org.junit.Assert;
import org.junit.Test;
import static org.mockito.Mockito.*;

import java.util.Random;

public class MockitoDemo {

    @Test
    public void test() {

        Random mockRandom = mock(Random.class);

        when(mockRandom.nextInt()).thenThrow(new RuntimeException("异常1"), new RuntimeException("异常2"));

        try {
            mockRandom.nextInt();
            Assert.fail();
        } catch (Exception ex) {
            Assert.assertTrue(ex instanceof RuntimeException);
            Assert.assertEquals("异常1", ex.getMessage());
        }

        try {
            mockRandom.nextInt();
            Assert.fail();
        } catch (Exception ex) {
            Assert.assertTrue(ex instanceof RuntimeException);
            Assert.assertEquals("异常2", ex.getMessage());
        }


    }

}

对应返回类型是 void 的函数,thenThrow 是无效的,要使用 Mockito 使用 doThrow 让方法抛出异常

使用then、thenAnswer 自定义方法处理逻辑

then 和 thenAnswer 的效果是一样的。它们的参数是实现 Answer 接口的对象,在改对象中可以获取调用参数,自定义返回值。

示例:

import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;

public class MockitoDemo {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }

    }

    @Mock
    private ExampleService exampleService;

    @Test
    public void test() {

        MockitoAnnotations.initMocks(this);

        when(exampleService.add(anyInt(),anyInt())).thenAnswer(new Answer<Integer>() {
            @Override
            public Integer answer(InvocationOnMock invocation) throws Throwable {
                Object[] args = invocation.getArguments();
                // 获取参数
                Integer a = (Integer) args[0];
                Integer b = (Integer) args[1];

                // 根据第1个参数,返回不同的值
                if (a == 1) {
                    return 9;
                }
                if (a == 2) {
                    return 99;
                }
                if (a == 3) {
                    throw new RuntimeException("异常");
                }
                return 999;
            }
        });

        Assert.assertEquals(9, exampleService.add(1, 100));
        Assert.assertEquals(99, exampleService.add(2, 100));

        try {
            exampleService.add(3, 100);
            Assert.fail();
        } catch (RuntimeException ex) {
            Assert.assertEquals("异常", ex.getMessage());
        }
    }


}

使用 doReturn 设置方法的返回值

doReturn 的作用和 thenReturn (Mockito 使用 thenReturn 设置方法的返回值) 相同,但使用方式不同:

import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;

import java.util.Random;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Test
    public void test() {

        MockitoAnnotations.initMocks(this);

        Random random = mock(Random.class);
        doReturn(1).when(random).nextInt();

        Assert.assertEquals(1, random.nextInt());

    }

}

使用 doThrow 让方法抛出异常

如果一个对象的方法的返回值是 void,那么不能用 when .. thenThrow 让该方法抛出异常

如果有返回值,

下面这种写法是错误的:

import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.when;

public class MockitoDemo {

    static class ExampleService {

        public void hello() {
            System.out.println("Hello");
        }

    }

    @Mock
    private ExampleService exampleService;

    @Test
    public void test() {

        MockitoAnnotations.initMocks(this);

        // 这句编译不通过,IDE 也会提示错误,原因很简单,when 的参数是非 void
        when(exampleService.hello()).thenThrow(new RuntimeException("异常"));

    }

}

用 doThrow 可以让返回void的函数抛出异常

换成下面的写法即可:

import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.doThrow;

public class MockitoDemo {

    static class ExampleService {

        public void hello() {
            System.out.println("Hello");
        }

    }

    @Mock
    private ExampleService exampleService;

    @Test
    public void test() {

        MockitoAnnotations.initMocks(this);

        // 这种写法可以达到效果
        doThrow(new RuntimeException("异常")).when(exampleService).hello();

        try {
            exampleService.hello();
            Assert.fail();
        } catch (RuntimeException ex) {
            Assert.assertEquals("异常", ex.getMessage());
        }
        
    }

}

也可以用 doThrow 让返回非void的函数抛出异常

import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;

import java.util.Random;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Test
    public void test() {

        MockitoAnnotations.initMocks(this);

        Random random = mock(Random.class);

        // 下面这句等同于 when(random.nextInt()).thenThrow(new RuntimeException("异常"));
        doThrow(new RuntimeException("异常")).when(random).nextInt();

        try {
            random.nextInt();
            Assert.fail();
        } catch (RuntimeException ex) {
            Assert.assertEquals("异常", ex.getMessage());
        }

    }

}

使用 doAnswer 自定义方法处理逻辑

doAnswer 的作用和 thenAnswer (Mockito 使用then、thenAnswer 自定义方法处理逻辑)相同,但使用方式不同:

import org.junit.Assert;
import org.junit.Test;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.util.Random;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    @Test
    public void test() {

        MockitoAnnotations.initMocks(this);

        Random random = mock(Random.class);
        doAnswer(new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                return 1;
            }
        }).when(random).nextInt();

        Assert.assertEquals(1, random.nextInt());

    }

}

使用 doNothing 让 void 函数什么都不做

doNothing 用于让 void 函数什么都不做。因为 mock 对象中,void 函数就是什么都不做,所以该方法更适合 spy 对象。

示例:

import org.junit.Test;
import static org.mockito.Mockito.*;

public class MockitoDemo {

    static class ExampleService {

        public void hello() {
            System.out.println("Hello");
        }

    }

    @Test
    public void test() {

        ExampleService exampleService = spy(new ExampleService());
        exampleService.hello();  // 会输出 Hello

        // 让 hello 什么都不做
        doNothing().when(exampleService).hello();
        exampleService.hello(); // 什么都不输出


    }
    
}

使用 MockitoJUnitRunner 运行 JUnit 测试

Mockito @Mock 注解 。主要作用是让 @Mock、@Spy 等注解生效。

使用 MockitoAnnotations.initMocks 让 @Mock 等注解生效

Mockito @Mock 注解

使用 reset 重置对象

使用 reset 方法,可以重置之前自定义的返回值和异常。

reset mock 对象示例

import org.junit.Assert;
import org.junit.Test;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }

    }

    @Test
    public void test() {

        ExampleService exampleService = mock(ExampleService.class);

        // mock 对象方法的默认返回值是返回类型的默认值
        Assert.assertEquals(0, exampleService.add(1, 2));

        // 设置让 add(1,2) 返回 100
        when(exampleService.add(1, 2)).thenReturn(100);
        Assert.assertEquals(100, exampleService.add(1, 2));

        // 重置 mock 对象,add(1,2) 返回 0
        reset(exampleService);
        Assert.assertEquals(0, exampleService.add(1, 2));

    }

}

reset spy 对象示例

import org.junit.Assert;
import org.junit.Test;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }

    }

    @Test
    public void test() {

        ExampleService exampleService = spy(new ExampleService());

        // spy 对象方法调用会用真实方法,所以这里返回 3
        Assert.assertEquals(3, exampleService.add(1, 2));

        // 设置让 add(1,2) 返回 100
        when(exampleService.add(1, 2)).thenReturn(100);
        Assert.assertEquals(100, exampleService.add(1, 2));

        // 重置 spy 对象,add(1,2) 返回 3
        reset(exampleService);
        Assert.assertEquals(3, exampleService.add(1, 2));

    }

}

使用 thenCallRealMethod 调用 spy 对象的真实方法

thenCallRealMethod 可以用来重置 spy 对象的特定方法特定参数调用。

示例:

import org.junit.Assert;
import org.junit.Test;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }

    }

    @Test
    public void test() {

        ExampleService exampleService = spy(new ExampleService());

        // spy 对象方法调用会用真实方法,所以这里返回 3
        Assert.assertEquals(3, exampleService.add(1, 2));

        // 设置让 add(1,2) 返回 100
        when(exampleService.add(1, 2)).thenReturn(100);
        when(exampleService.add(2, 2)).thenReturn(100);
        Assert.assertEquals(100, exampleService.add(1, 2));
        Assert.assertEquals(100, exampleService.add(2, 2));

        // 重置 spy 对象,让 add(1,2) 调用真实方法,返回 3
        when(exampleService.add(1, 2)).thenCallRealMethod();
        Assert.assertEquals(3, exampleService.add(1, 2));

        // add(2, 2) 还是返回 100
        Assert.assertEquals(100, exampleService.add(2, 2));
    }
    
}

使用 verify 校验是否发生过某些操作

使用 verify 可以校验 mock 对象是否发生过某些操作

示例

import org.junit.Test;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }

    }

    @Test
    public void test() {

        ExampleService exampleService = mock(ExampleService.class);

        // 设置让 add(1,2) 返回 100
        when(exampleService.add(1, 2)).thenReturn(100);

        exampleService.add(1, 2);

        // 校验是否调用过 add(1, 2) -> 校验通过
        verify(exampleService).add(1, 2);

        // 校验是否调用过 add(2, 2) -> 校验不通过
        verify(exampleService).add(2, 2);

    }

}

verify 配合 time 方法,可以校验某些操作发生的次数

示例:

import org.junit.Test;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }

    }

    @Test
    public void test() {

        ExampleService exampleService = mock(ExampleService.class);

        // 第1次调用
        exampleService.add(1, 2);

        // 校验是否调用过一次 add(1, 2) -> 校验通过
        verify(exampleService, times(1)).add(1, 2);

        // 第2次调用
        exampleService.add(1, 2);

        // 校验是否调用过两次 add(1, 2) -> 校验通过
        verify(exampleService, times(2)).add(1, 2);

    }

}

使用 mockingDetails 方法判断对象是否为 mock对象、spy 对象

Mockito 的 mockingDetails 方法会返回 MockingDetails 对象,它的 isMock 方法可以判断对象是否为 mock 对象,isSpy 方法可以判断对象是否为 spy 对象。

示例:

import org.junit.Test;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }

    }

    @Test
    public void test() {

        ExampleService exampleService = mock(ExampleService.class);

        // 判断 exampleService 是否为 mock 对象
        System.out.println( mockingDetails(exampleService).isMock() );     // true

        // 判断 exampleService 是否为 spy 对象
        System.out.println( mockingDetails(exampleService).isSpy() );      // false

    }

}

链式调用

thenReturn、doReturn 等函数支持链式调用,用来指定函数特定调用次数时的行为。

示例1:

import org.junit.Assert;
import org.junit.Test;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }

    }

    @Test
    public void test() {

        ExampleService exampleService = mock(ExampleService.class);

        // 让第1次调用返回 100,第2次调用返回 200
        when(exampleService.add(1, 2)).thenReturn(100).thenReturn(200);

        Assert.assertEquals(100, exampleService.add(1, 2));
        Assert.assertEquals(200, exampleService.add(1, 2));
        Assert.assertEquals(200, exampleService.add(1, 2));

    }

}
import org.junit.Assert;
import org.junit.Test;

import static org.mockito.Mockito.*;

public class MockitoDemo {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }

    }

    @Test
    public void test() {

        ExampleService exampleService = mock(ExampleService.class);

        // 让第1次调用返回 100,第2次调用返回 200
        doReturn(100).doReturn(200).when(exampleService).add(1, 2);

        Assert.assertEquals(100, exampleService.add(1, 2));
        Assert.assertEquals(200, exampleService.add(1, 2));
        Assert.assertEquals(200, exampleService.add(1, 2));

    }

}

测试隔离

根据 JUnit 单测隔离 ,当 Mockito 和 JUnit 配合使用时,也会将非static变量或者非单例隔离开。

比如使用 @Mock 修饰的 mock 对象在不同的单测中会被隔离开。

示例:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {

    static class ExampleService {

        public int add(int a, int b) {
            return a+b;
        }

    }

    @Mock
    private ExampleService exampleService;

    @Test
    public void test01() {
        System.out.println("---call test01---");

        System.out.println("打桩前: " + exampleService.add(1, 2));

        when(exampleService.add(1, 2)).thenReturn(100);

        System.out.println("打桩后: " + exampleService.add(1, 2));
    }

    @Test
    public void test02() {
        System.out.println("---call test02---");

        System.out.println("打桩前: " + exampleService.add(1, 2));

        when(exampleService.add(1, 2)).thenReturn(100);

        System.out.println("打桩后: " + exampleService.add(1, 2));
    }

}

将两个单测一起运行,运行结果是:

---call test01---
打桩前: 0
打桩后: 100
---call test02---
打桩前: 0
打桩后: 100

test01 先被执行,打桩前调用add(1, 2)的结果是0,打桩后是 100。

然后 test02 被执行,打桩前调用add(1, 2)的结果是0,而非 100,这证明了我们上面的说法。

使用 PowerMock 让 Mockito 支持静态方法

PowerMock 是一个增强库,用来增加 Mockito 、EasyMock 等测试库的功能。

Mockito 默认是不支持静态方法

比如我们在 ExampleService 类中定义静态方法 add:

public class ExampleService {

        public static int add(int a, int b) {
            return a+b;
        }

    }

尝试给静态方法打桩,会报错:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class MockitoDemo {

    @Test
    public void test() {
        
        // 会报错
        when(ExampleService.add(1, 2)).thenReturn(100);

    }

}

可以用 Powermock 弥补 Mockito 缺失的静态方法 mock 功能

在 build.gradle 中配置以下依赖:

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    testCompile group: 'org.mockito', name: 'mockito-core', version: '2.25.1'

    // PowerMock 相关依赖
    testCompile group: 'org.powermock', name: 'powermock-core', version: '2.0.0'
    testCompile group: 'org.powermock', name: 'powermock-module-junit4', version: '2.0.0'
    testCompile group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.0'
}

示例:

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.mockito.Mockito.*;

@RunWith(PowerMockRunner.class)     // 这是必须的
@PrepareForTest(ExampleService.class)  // 声明要处理 ExampleService
public class MockitoDemo {
    @Test
    public void test() {

        PowerMockito.mockStatic(ExampleService.class);  // 这也是必须的

        when(ExampleService.add(1, 2)).thenReturn(100);

        Assert.assertEquals(100, ExampleService.add(1, 2));
        Assert.assertEquals(0, ExampleService.add(2, 2));

    }
}

PowerMockRunner 支持 Mockito 的 @Mock 等注解

上面我们用了 PowerMockRunner ,MockitoJUnitRunner 就不能用了。但不要担心, @Mock 等注解还能用。

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.powermock.modules.junit4.PowerMockRunner;

import java.util.Random;

import static org.mockito.Mockito.*;

@RunWith(PowerMockRunner.class)
public class MockitoDemo {

    @Mock
    private Random random;

    @Test
    public void test() {

        when(random.nextInt()).thenReturn(1);
        Assert.assertEquals(1,  random.nextInt());

    }
}

如何临时 mock 对象

如果需要临时将一个对象的内部对象替换为 mock 对象,在无法通过set和get处理内部对象的情况下,可以利用反射搞定。

Java JOOR 反射库 是一个很好用的反射库。本文用它进行临时替换。

用一个小项目作为示例:

项目结构:

.
├── build.gradle
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── demo
    │   │       ├── HttpService.java
    │   │       └── BizService.java
    │   └── resources
    └── test
        ├── java
        │   └── demo
        │       └── BizServiceTest.java
        └── resources

build.gradle 中声明的依赖:

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    testCompile group: 'org.mockito', name: 'mockito-core', version: '2.25.1'
    testCompile group: 'org.jooq', name: 'joor-java-8', version: '0.9.7'
}

HttpService 类:

package demo;

public class HttpService {

    public int queryStatus() {
        // 发起网络请求,提取返回结果
        // 这里直接返回0
        return 0;
    }

}

BizService 类:

package demo;

public class BizService {

    private HttpService httpService = new HttpService();

    public String hello() {
        int status = httpService.queryStatus();
        if (status == 0) {
            return "你好";
        }
        else if (status == 1) {
            return "Hello";
        }
        else {
            return "未知状态";
        }
    }

}

BizServiceTest 测试类:

package demo;

import org.joor.Reflect;
import org.junit.Test;

import static org.mockito.Mockito.*;

public class BizServiceTest {

    private BizService bizService = new BizService();

    @Test
    public void testHello() {

        System.out.println( bizService.hello() );  // 输出'你好'

        // 取出原有的对象
        Object realHttpService = Reflect.on(bizService).get("httpService");

        // 创建 mock 对象,并用它替换掉 bizService 中的 httpService 对象
        HttpService mockHttpService = mock(HttpService.class);
        when(mockHttpService.queryStatus()).thenReturn(1);
        Reflect.on(bizService).set("httpService", mockHttpService);

        System.out.println( bizService.hello() );  // 输出'hello'

        // 再将原先的对象设置回去
        Reflect.on(bizService).set("httpService", realHttpService);
        System.out.println( bizService.hello() );  // 输出'你好'

    }

}


( 本文完 )