Java 学习笔记

所有文章
📖 最新文章 transmittable-thread-local 库 JOOR 反射库 Lombok 库:为你减少样板代码 Slf4j 日志组件的使用 加速maven、gradle依赖下载
📖 Java 基础 安装 第一个程序 使用 UTF-8 编写代码 空值 null 正则表达式 线程 日期/时间 匿名类 枚举 ThreadLocal 线程本地变量 动态代理 jar 命令

Java Mockito 测试框架快速入门


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 是无效的,要使用 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 相同,但使用方式不同:

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 相同,但使用方式不同:

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() );  // 输出'你好'

    }

}

( 本文完 )

文章目录