1. Mockito
单元测试开发中,我们经常会遇到测试的类有很多依赖的类、对象、资源,从而形成巨大的依赖树,mock可以模拟外部依赖,适应单元测试。
比如我们在开发中很容易出现这种情况:
(a)依赖(b)依赖(c)依赖(d)
这种情况下单元测试就变得极其复杂,而且如果需要测试数据库或网络请求返回值的情况,就更加复杂了。我们总不至于去修改真实数据库,去修改真实的网络情况吧。
但是如果我们引入Mockito情况就会变得简单许多,我们可以mock虚拟的依赖,并可以轻松的修改返回值甚至抛出异常:
(a)依赖(mock b)
1.1 Mockito 的简单用法
Mockito是一个功能非常丰富的框架,下面介绍几个典型和常用的方法,详细的使用可以到官网上查询。
Mockito官网{:target="_blank"}
Mocktio文档{:target="_blank"}
我们在非Spring Boot项目中使用Mockito需要手动引入依赖,Spring Boot项目中Mocktio的使用会在下面介绍。
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
1.1.1 mock对象
// 模拟LinkedList 的一个对象
LinkedList mockedList = mock(LinkedList.class);
// 此时调用get方法,会返回null,因为还没有对方法调用的返回值做模拟
System.out.println(mockedList.get(0));
1.1.2 方法调用的返回值
// 模拟获取第一个元素时,返回字符串first。 给特定的方法调用返回固定值在官方说法中称为stub。
when(mockedList.get(0)).thenReturn("first");
// 此时打印输出first
System.out.println(mockedList.get(0));
1.1.3 方法调用抛出异常
// 模拟获取第二个元素时,抛出RuntimeException
when(mockedList.get(1)).thenThrow(new RuntimeException());
// 此时将会抛出RuntimeException
System.out.println(mockedList.get(1));
如果一个方法没有返回值类型,那么可以使用此方法模拟异常抛出
doThrow(new RuntimeException("clear exception")).when(mockedList).clear();
mockedList.clear();
1.1.4 调用方法时的参数匹配
// anyInt()匹配任何int参数,这意味着参数为任意值,其返回值均是element
when(mockedList.get(anyInt())).thenReturn("element");
// 此时打印是element
System.out.println(mockedList.get(999));
1.1.5 验证方法调用次数
// 调用add一次
mockedList.add("once");
// 下面两个写法验证效果一样,均验证add方法是否被调用了一次
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
1.1.6 校验行为
校验方法调用
// mock creation
List mockedList = mock(List.class);
// using mock object
mockedList.add("one");
mockedList.clear();
// verification
verify(mockedList).add("one");
verify(mockedList).clear();
校验方法调用顺序
// A. Single mock whose methods must be invoked in a particular order
List singleMock = mock(List.class);
//using a single mock
singleMock.add("was added first");
singleMock.add("was added second");
//create an inOrder verifier for a single mock
InOrder inOrder = inOrder(singleMock);
//following will make sure that add is first called with "was added first, then with "was added second"
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
// B. Multiple mocks that must be used in a particular order
List firstMock = mock(List.class);
List secondMock = mock(List.class);
//using mocks
firstMock.add("was called first");
secondMock.add("was called second");
//create inOrder object passing any mocks that need to be verified in order
InOrder inOrder = inOrder(firstMock, secondMock);
//following will make sure that firstMock was called before secondMock
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");
// Oh, and A + B can be mixed together at will
校验方法是否从未调用
//using mocks - only mockOne is interacted
mockOne.add("one");
//ordinary verification
verify(mockOne).add("one");
//verify that method was never called on a mock
verify(mockOne, never()).add("two");
//verify that other mocks were not interacted
verifyZeroInteractions(mockTwo, mockThree);
1.2 Mockito 在 Spring Boot 中的使用
上面我们提到了非 Spring Boot 需要手动引入 Mocktio 的依赖,但在 Spring Boot 项目中,Spring Boot Test 默认依赖 Mocktio ,所以只要依赖了 Spring Boot Test 就不再需要我们手动引入依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
一般情况下,单元测试不需要 Spring 的启动。此时我们按下面的写法:
import static org.mockito.Mockito.*;
public class MyServiceTest {
/**被单元测试的类*/
@InjectMocks
private MyServiceImpl myServiceImpl;
/**被单元测试的类中所有的依赖*/
@Mock
private MyMapper myMapper;
/**在单元测试之前,执行Mockito的注解扫描,并注入依赖*/
@Before
public void setUp() { MockitoAnnotations.initMocks(this); }
@Test
public void myServiceMethod() {
myServiceImpl.myServiceMethod();
}
}
或者通过注解 RunWith
的方式:
import static org.mockito.Mockito.*;
@RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {
/**被单元测试的类*/
@InjectMocks
private MyServiceImpl myServiceImpl;
/**被单元测试的类中所有的依赖*/
@Mock
private MyMapper myMapper;
@Test
public void myServiceMethod() {
myServiceImpl.myServiceMethod();
}
}
但有些情况下,我们可能需要 Spring 启动着。可以按下面这样写:
import static org.mockito.Mockito.*;
@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class MyServiceTest {
/**被单元测试的类*/
@InjectMocks
private MyServiceImpl myServiceImpl;
/**被单元测试的类中所有的依赖*/
@Mock
private MyMapper myMapper;
@Test
public void myServiceMethod() {
myServiceImpl.myServiceMethod();
}
}
2. PowerMock
Mocktio 是一款轻巧方便的 mock 工具,能够完成 90% 的单元测试要求,但是确无法完成静态方法、构造方法、私有方法、final 方法以及系统方法的模拟。因此我们在碰到那 10% 的无法完成时引入了 PowerMock 。PowerMock 并不是对 Mocktio 的替代,而是补充和完善。PowerMock 有多个版本,分别是对多个 mock 工具的补充,此处我们仅介绍 PowerMock 的 Mocktio 和 Junit4 版本,其余版本类似。
PowerMock 官网{:target="_blank"}
首先引入依赖:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<scope>test</scope>
</dependency>
此处有一点需要注意,PowerMock 的版本与 Mocktio 的版本是对应的,如果用错了版本,会出现一些莫名其妙的错误。
PowerMock 与 Mockito 版本{:target="_blank"}
Mockito | PowerMock |
---|---|
2.8.0-2.8.9 | 1.7.x |
2.7.5 | 1.7.0RC4 |
2.4.0 | 1.7.0RC2 |
2.0.0-beta - 2.0.42-beta | 1.6.5-1.7.0RC |
1.10.8 - 1.10.x | 1.6.2 - 2.0 |
1.9.5-rc1 - 1.9.5 | 1.5.0 - 1.5.6 |
1.9.0-rc1 & 1.9.0 | 1.4.10 - 1.4.12 |
1.8.5 | 1.3.9 - 1.4.9 |
1.8.4 | 1.3.7 & 1.3.8 |
1.8.3 | 1.3.6 |
1.8.1 & 1.8.2 | 1.3.5 |
1.8 | 1.3 |
1.7 | 1.2.5 |
2.1 PowerMock 的简单用法
具体使用与 Mocktio 在 SpringBoot 中类似,如下:
import static org.mockito.Matchers.*;
import static org.powermock.api.mockito.PowerMockito.*;
//如果需要spring运行,则使用此注解
//@SpringBootTest
//配置PowerMock的启动器,并配置代理原先的SpringJUnit4ClassRunner启动器
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
//配置需要PowerMock进行mock的类
@PrepareForTest(value = {StringUtils.class})
//配置PowerMock的classLoader忽略该路径下的类
@PowerMockIgnore({"javax.management.*"})
public class MyServiceTest {
/**被单元测试的类*/
@InjectMocks
private MyServiceImpl myServiceImpl;
/**被单元测试的类中所有的依赖*/
@Mock
private MyMapper myMapper;
@Test
public void myServiceMethod() {
mockStatic(StringUtils.class);
when(StringUtils.isEmpty(any())).thenReturn(true);
myServiceImpl.myServiceMethod();
}
}
- 我们依然使用
org.mockito.Matchers
下的any()
等方法进行匹配,但是when()
,thenReturn()
等方法就得使用 PowerMock 的方法了。 - 配置
RunWith
使用PowerMockRunner
代理原来的SpringJUnit4ClassRunner
,因为 PowerMock 需要在运行之前做一些处理。 - 通过
PrepareForTest
指定需要 PowerMock 进行特殊 mock 的类。例如,你想 mock 的静态方法、构造方法、私有方法、final 方法以及系统方法所在的类。 - 通过
PowerMockIgnore
配置需要 PowerMock 的 classLoader 忽略的类,因为PowerMock 为了实现这些特殊方法的 mock ,需要借助自定义的 classLoader 。
2.1.1 mock 方法内部 new 出来的对象
目标代码:
public class MyServiceImpl {
public file createFile(String path){
return new File(path);
}
}
测试代码:
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyServiceImpl.class)
public class MyServiceImplTest {
@Test
public void createFile() {
File file = new File;
PowerMockito.whenNew(File.class).withArguments("abc").thenReturn(file);
MyServiceImpl myService = new MyServiceImpl();
Assert.isTrue(myService.createFile("abc") == file);
}
}
2.1.2 mock 普通类的静态方法
目标代码:
public class ListUtil {
public static boolean isEmpty(List list) {
return list.isEmpty();
}
}
测试代码:
@RunWith(PowerMockRunner.class)
@PrepareForTest(ListUtil.class)
public class ListUtilTest {
@Test
public void isEmpty(){
PowerMockito.mockStatic(ListUtil.class);
PowerMockito.when(ListUtil.isEmpty(any())).thenReturn(false);
List list = new ArrayList();
Assert.isTrue(!ListUtil.isEmpty(list));
}
}
2.1.3 mock 私有方法
目标代码:
public class MyServiceImpl {
public String add(String a, String b) {
return stringToInt(a) + stringToInt(b) + "";
}
private int stringToInt(String str) {
return Integer.valueOf(str);
}
}
测试代码:
@RunWith(PowerMockRunner.class)
@PrepareForTest(MyServiceImpl.class)
public class MyServiceImplTest {
@Test
public void createFile() {
MyServiceImpl myService = PowerMockito.mock(MyServiceImpl.class);
PowerMockito.when(myService, "stringToInt").thenReturn(0);
PowerMockito.when(myService.add(any(), any())).thenCallRealMethod();
Assert.isTrue(myService.add(null));
}
}