Mockito 是一个模拟测试框架。主要功能是模拟类/对象的行为。Mockito 一般用于控制调用外部的返回值,让我们只关心和测试自己的业务逻辑。
关于 Mockito 的具体使用方法可以参考 Mocktio 入门。
我们用一个示例说明 Spring 中如何使用 Mockito 进行测试。
示例
项目结构
.
├── build.gradle
├── settings.gradle
└── src
├── main
│ ├── java
│ │ └── demo
│ │ ├── AppConfig.java
│ │ ├── BusinessService.java
│ │ ├── HttpService.java
│ │ └── Main.java
│ └── resources
└── test
└── java
└── demo
├── BusinessServiceTest01.java
└── BusinessServiceTest02.java
build.gradle
build.gradle 中配置的依赖如下:
dependencies {
compile group: 'org.springframework', name: 'spring-context', version: '5.0.6.RELEASE'
testCompile group: 'org.springframework', name: 'spring-test', version: '5.0.6.RELEASE'
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile group: 'org.mockito', name: 'mockito-core', version: '2.25.1'
}
src/main/java/demo 中是一个简单的 Spring 项目,各个类的内容如下:
HttpService.java
package demo;
import org.springframework.stereotype.Service;
import java.util.Random;
@Service
public class HttpService {
public int queryStatus() {
// 发起网络请求,得到响应值,然后返回
// 这里用随机数模拟
return new Random().nextInt(2);
}
}
BusinessService.java
package demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class BusinessService {
@Autowired
private HttpService httpService;
public String hello() {
int status = httpService.queryStatus();
if (status == 0) {
return "你好";
}
else {
return "Hello";
}
}
}
AppConfig.java
package demo;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class AppConfig {
}
Main.java
package demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
BusinessService businessService = applicationContext.getBean(BusinessService.class);
System.out.println(businessService.hello()); // 输出"你好",或者"Hello"
}
}
src/test/java/demo 编写了两个关于 BusinessService 的测试类。
测试示例1:使用 ReflectionTestUtils 设置 mock 对象
BusinessServiceTest01 类内容如下:
package demo;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=AppConfig.class)
public class BusinessServiceTest01 {
@Autowired
private BusinessService businessService;
@Test
public void testHello() {
// 取出真实的 httpService
HttpService realHttpService = (HttpService) ReflectionTestUtils.getField(businessService, "httpService");
// 生成 HttpService 的 mock 对象,替换掉 businessService 的真实对象
HttpService mockHttpService = Mockito.mock(HttpService.class);
ReflectionTestUtils.setField(businessService, "httpService", mockHttpService);
// 给 mock 对象打桩
Mockito.when(mockHttpService.queryStatus()).thenReturn(0);
// 测试
Assert.assertEquals("你好", businessService.hello()); // 永远都返回 "你好"
// 恢复真实的 httpService
ReflectionTestUtils.setField(businessService, "httpService", realHttpService);
}
}
这种使用方式过于繁琐,下面的示例,更简洁。
测试示例2:使用 @Mock、@InjectMocks 自动注入 mock 对象
package demo;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.util.ReflectionTestUtils;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=AppConfig.class)
public class BusinessServiceTest02 {
@InjectMocks
@Autowired
private BusinessService businessService;
@Mock
private HttpService mockHttpService;
@Before
public void before() {
MockitoAnnotations.initMocks(this); // 这个必须要有
}
@Test
public void testHello() {
// 给 mock 对象打桩
Mockito.when(mockHttpService.queryStatus()).thenReturn(0);
// 测试
Assert.assertEquals("你好", businessService.hello()); // 永远都返回 "你好"
}
}
这种使用方式更加简洁。
但要注意:
第1点: businessService 是一个 Spring 单例 Bean,其中的真实 httpService 被 Mockito 替换成了 mock 对象。在较大的项目中会有很多单元测试用例,如果 BusinessServiceTest02 之后还有测试类要用到 businessService,它内部的 httpService 都会是 mock 对象。
第2点:
如果 BusinessService 内部使用了 @Transcational 或者接入了切面,那么 businessService 对象会是一个代理对象,此时,@InjectMocks 的方式是无效的。因为只是将 mock 对象注入到了代理对象中,没有注入到被代理对象中。
怎么办?用示例1中的ReflectionTestUtils.setField
,该方法会将 mock 对象注入到被代理对象中。ReflectionTestUtils.setField
在较旧的 spring-test 版本中不支持注入到被代理对象。判断是否支持,看 setField 源码中有没有下面的内容:
if (targetObject != null && springAopPresent) {
targetObject = AopTestUtils.getUltimateTargetObject(targetObject);
}
如果因为一些原因无法升级 Spring 来解决这种问题,可以参考 UT中使用ReflectionTestUtils.setField不能mock掉依赖问题解决 。