不积跬步无以至千里,不积小流无以成江海
一、背景
- 在日常开发编码中,经常会遇到这样的场景:开发接口的过程中,有部分数据依赖同事开发的接口,需要等到同事的接口开发完成以后,才能开始我们的功能开发。这样依赖同事,有时候会影响到项目的上线节奏,所以我们在寻找有没能够解决这种场景的工具。
- 或者说,我们在分析完业务需求后,设计验证大的业务流程的代码的时候,也可以用到此类工具。首先把整体的大的处理逻辑完成,把细枝末节的处理打上标记 ToDo。
- Mockito 可以帮助我们在编写单元测试类的时候,调用指定接口返回指定的数据信息。通过模拟数据,替代真实数据,这样我们不需要等到同事的接口开发完成后,再开始我们的工作,做到“并行开发”。
二、实施过程
1. 引入依赖
1 2 3 4 5 6 7
| <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>1.9.5</version> <scope>test</scope> </dependency>
|
2. CURD Spring MVC 业务代码(略)
3. 基于 Mockito 的单元测试基类
- 一般使用项目中已经写好的测试基类即可。
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
| @RunWith(SpringJUnit4ClassRunner.class)//表示整合JUnit4进行测试 @ContextConfiguration(locations={ "classpath:spring/applicationContext.xml", "classpath:spring/applicationContext-persistent.xml"}) public class BaseSpringTest { private Gson gson = new GsonBuilder().setPrettyPrinting().create();
public void printlnJson(Object object) { System.out.println(gson.toJson(object)); }
@Autowired private UserService userService;
public LoginUser mockLoginUser(String userId){ LoginUser loginUser = new LoginUser();
UserBaseV userBaseV = userService.queryOne(userId);
User user = new User();
BeanUtil.copyProperties(user, userBaseV); UserUtil.currentUser.set(user);
PreconditionsUtil.checkNotNull(userBaseV, "查询失败, 未查询到对应的用户数据");
loginUser.setId(userBaseV.getId()); loginUser.setLoginName(userBaseV.getUsername()); loginUser.setDealerId(userBaseV.getDealerId()); loginUser.setOrgId(userBaseV.getOrganizationId()); loginUser.setUserName(userBaseV.getName());
return loginUser; } }
|
4. Service 对应的 Mockito 测试类
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
| import com.google.common.collect.Lists; import com.google.gson.Gson; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock;
public class MockitoBusinessTypeTest extends BaseMockitoSpringTest { @InjectMocks private BusinessTypeService businessTypeService = mock(BusinessTypeService.class); @Mock private IvrDealerSettingService ivrDealerSettingService;
@Mock private LOVService lovService;
@Autowired private SeatService seatService;
@Before public void initMock(){ MockitoAnnotations.initMocks(this); doReturn(Lists.newArrayList()).when(businessTypeService).getSatisfactionBusinessType(); doReturn("hello world").when(ivrDealerSettingService).getSkillGroupByDealerAndIvrKey("a", "b", "c");
BusinessTypeV businessTypeResult = new BusinessTypeV(); businessTypeResult.setCode("11"); doReturn(businessTypeResult).when(businessTypeService).getUserDefaultBusinessType("system"); }
@Test public void queryTest(){ List<BusinessTypeLOV> list = businessTypeService.getSatisfactionBusinessType(); String ivrResult = ivrDealerSettingService.getSkillGroupByDealerAndIvrKey("a", "b", "c"); System.out.println(new Gson().toJson(list)); System.out.println(ivrResult); }
@Test public void seatQueryTest(){ String businessType = businessTypeService.getUserDefaultBusinessType("system").getCode(); printlnJson(seatService.selectByBusinessType(businessType)); } }
|
5. Controller 对应的 Mockito 测试类
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
|
@RunWith(MockitoJUnitRunner.class) public class MockitoBusinessTypeControllerTest {
private MockMvc mockMvc;
@InjectMocks private BusinessTypeController businessTypeController; @Mock private BusinessTypeService businessTypeService; @Mock private ClusterMessageListener clusterMessageListener;
@Before public void initMock() { MockitoAnnotations.initMocks(this); mockMvc = MockMvcBuilders.standaloneSetup(businessTypeController).build(); }
@Test public void getBusinessInfoTest() throws Exception { BusinessTypeV entity = new BusinessTypeV(); entity.setCode("11"); when(businessTypeService.queryOne(Mockito.any())).thenReturn(entity);
mockMvc.perform(get("/rest/businessType/all/getBusinessInfo") .param("businessType", "11") ) .andDo(print()) .andExpect(status().isOk()); } }
|
6. 注解的说明
首先是Spring的几个Annotate:
- RunWith(SpringJUnit4ClassRunner.class): 表示使用Spring Test组件进行单元测试;
- WebAppConfiguration: 使用这个Annotate会在跑单元测试的时候真实的启一个web服务,然后开始调用Controller的Rest API,待单元测试跑完之后再将web服务停掉;
- ContextConfiguration: 指定Bean的配置文件信息,可以有多种方式,这个例子使用的是文件路径形式,如果有多个配置文件,可以将括号中的信息配置为一个字符串数组来表示;
然后是Mockito的Annotate:
- Mock: 如果该对象需要mock,则加上此Annotate;
- InjectMocks: 使mock对象的使用类可以注入mock对象,在上面这个例子中,mock对象是UserService,使用了UserService的是UserController,所以在Controller加上该Annotate;
Setup方法:
- MockitoAnnotations.initMocks(this): 将打上Mockito标签的对象起作用,使得Mock的类被Mock,使用了Mock对象的类自动与Mock对象关联。
- mockMvc: 细心的朋友应该注意到了这个对象,这个对象是Controller单元测试的关键,它的初始化也是在setup方法里面。
测试用例:
- mockMvc.perform: 发起一个http请求。
- post(url): 表示一个post请求,url对应的是Controller中被测方法的Rest url。
- param(key, value): 表示一个request parameter,方法参数是key和value。
- andDo(print()): 表示打印出request和response的详细信息,便于调试。
- andExpect(status().isOk()): 表示期望返回的Response Status是200。
- andExpect(content().string(is(expectstring)): 表示期望返回的Response Body内容是期望的字符串。
三、额外
Java 单元测试 PowerMock
文章对 PowerMock 做了非常详尽的讲解,非常推荐。
PowerMock也是依赖 Mockito,所以我认为这两个框架用起来的差别应该不大。