官网http://mockito.org/
API http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html
抄笔记,https://www.letianbiji.com/java-mockito/mockito-test-isolate.html
基本概念
单元测试UT
工作一段时间后,才真正意识到代码质量的重要性。虽然囫囵吞枣式地开发,表面上看来速度很快,但是给后续的维护与拓展制造了很多隐患。
作为一个想专业但还不专业的程序员,通过构建覆盖率比较高的单元测试用例,可以比较显著地提高代码质量。如后续需求变更、版本迭代时,重新跑一次单元测试即可校验自己的改动是否正确。
是什么
Mockito是mocking框架,它让你用简洁的API做测试。而且Mockito简单易学,它可读性强和验证语法简洁。
Stub和Mock异同
相同:Stub和Mock都是模拟外部依赖
不同:Stub是完全模拟一个外部依赖, 而Mock还可以用来判断测试通过还是失败
与Junit框架一同使用
请MockitoAnnotations.initMocks(testClass); 必须至少使用一次。 要处理注释,我们可以使用内置的运行器MockitoJUnitRunner或规则MockitoRule 。 我们还可以在@Before注释的Junit方法中显式调用initMocks()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @RunWith(MockitoJUnitRunner.class) public class ApplicationTest { }
public class ApplicationTest { @Rule public MockitoRule rule = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); }
public class ApplicationTest { @Before public void init() { MockitoAnnotations.initMocks(this); } }
|
使得@Mock和@Spy等注解生效
使用限制
- 不能mock静态方法
- 不能mock private方法
- 不能mock final class
2 使用教程
添加依赖
在spring-boot-starter-test中包含这两个依赖
1 2 3 4 5 6 7 8 9 10 11 12
| <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.9.5</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency>
|
添加引用
1 2 3
| import static org.mockito.Mockito.*; import static org.junit.Assert.*;
|
典型事例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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() { HttpService mockHttpService = mock(HttpService.class); when(mockHttpService.queryStatus()).thenReturn(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。
mock 对象的方法的返回值默认都是返回类型的默认值。例如,返回类型是 int,默认返回值是 0;返回类型是一个类,默认返回值是 null。
mock 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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 public void when_thenReturn(){ Iterator iterator = mock(Iterator.class); when(iterator.next()).thenReturn("hello").thenReturn("world"); String result = iterator.next() + " " + iterator.next() + " " + iterator.next(); assertEquals("hello world world",result); }
|
mock接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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");
Assert.assertEquals(0, mockList.size()); Assert.assertEquals(null, mockList.get(0));
when(mockList.get(0)).thenReturn("a");
Assert.assertEquals(0, mockList.size()); Assert.assertEquals("a", mockList.get(0));
Assert.assertEquals(null, mockList.get(1)); } }
|
@Mock注解
@Mock 注解可以理解为对 mock 方法的一个替代。
使用该注解时,要使用MockitoAnnotations.initMocks 方法,让注解生效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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()); }
}
|
3 when参数匹配
参数匹配器可以用在when和verify的方法中。
精确匹配
其中when(mockList.get(0)).thenReturn(“a”); 指定了get(0)的返回值,这个 0 就是参数的精确匹配。我们还可以让不同的参数对应不同的返回值,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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));
}
}
|
模糊匹配
可以使用 Mockito.anyInt() 匹配所有类型为 int 的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| 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");
Assert.assertEquals("a", mockStringList.get(0)); Assert.assertEquals("a", mockStringList.get(1));
}
@Test public void mockStudent() { Student student = mock(Student.class);
student.sleep(1, "1", "admin");
verify(student).sleep(anyInt(), anyString(), eq("admin")); verify(student).sleep(anyInt(), anyString(), eq("admin")); } }
|
4 then返回值
thenReturn 设置方法的返回值
thenReturn 用来指定特定函数和参数调用的返回值。
比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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 中可以指定多个返回值。在调用时返回值依次出现。若调用次数超过返回值的数量,再次调用时返回最后一个返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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 用来让函数调用抛出异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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 中可以指定多个异常。在调用时异常依次出现。若调用次数超过异常的数量,再次调用时抛出最后一个异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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()); }
}
}
|
then、thenAnswer 自定义方法处理逻辑
then 和 thenAnswer 的效果是一样的。它们的参数是实现 Answer 接口的对象,在改对象中可以获取调用参数,自定义返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| 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];
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()); } }
}
|
5 verify校验操作
使用 verify 可以校验 mock 对象是否发生过某些操作
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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);
when(exampleService.add(1, 2)).thenReturn(100);
exampleService.add(1, 2);
verify(exampleService).add(1, 2);
verify(exampleService).add(2, 2);
}
}
|
verify 配合 time 方法,可以校验某些操作发生的次数
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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.add(1, 2);
verify(exampleService, times(1)).add(1, 2);
exampleService.add(1, 2);
verify(exampleService, times(2)).add(1, 2);
}
}
|
6 Spy
spy 和 mock不同,不同点是:
- spy 的参数是对象示例,mock 的参数是 class。
- 被 spy 的对象,调用其方法时默认会走真实方法。mock 对象不会。
Spy对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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 {
@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));
}
@Test public void test_mock() {
ExampleService mockExampleService = mock(ExampleService.class);
Assert.assertEquals(0, mockExampleService.add(1, 2));
}
}
|
@Spy
spy 对应注解 @Spy,和 @Mock 是一样用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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初始化
对于@Spy,如果发现修饰的变量是 null,会自动调用类的无参构造函数来初始化。如果没有无参构造函数,必须使用写法2。
1 2 3 4 5 6 7
| // 写法1 @Spy private ExampleService spyExampleService;
// 写法2 @Spy private ExampleService spyExampleService = new ExampleService();
|
7 @InjectMocks
mockito 会将 @Mock、@Spy 修饰的对象自动注入到 @InjectMocks 修饰的对象中。
注入方式有多种,mockito 会按照下面的顺序尝试注入:
- 构造函数注入
- 设值函数注入(set函数)
- 属性注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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 "未知状态"; } }
}
|
编写测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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();
@Test public void test01() {
MockitoAnnotations.initMocks(this);
when(httpService.queryStatus()).thenReturn(0);
Assert.assertEquals("你好", exampleService.hello());
}
}
|
8 链式调用
thenReturn、doReturn 等函数支持链式调用,用来指定函数特定调用次数时的行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| 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);
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));
}
}
|
9 Springboot与Mockito
- spring-boot-starter-test中已经加入了Mockito依赖,所以我们无需手动引入。
- 另外要注意一点,在SpringBoot环境下,我们可能会用@SpringBootTest注解。
1 2 3 4 5 6 7
| @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @BootstrapWith(SpringBootTestContextBootstrapper.class) @ExtendWith({SpringExtension.class}) public @interface SpringBootTest {
|
如果用这个注解,跑单元测试的时候会加载SpringBoot的上下文,初始化Spring容器一次,显得格外的慢,这可能也是很多人放弃在Spring环境下使用单元测试的原因之一。
不过我们可以不用这个Spring环境,单元测试的目的应该是只测试这一个函数的逻辑正确性,某些容器中的相关依赖可以通过Mockito仿真。
所以我们可以直接拓展自MockitoExtendsion,这样跑测试就很快了。
1 2 3
| @ExtendWith(MockitoExtension.class) public class ListMockTest { }
|