一、背景

  1. 在日常开发编码中,经常会遇到这样的场景:开发接口的过程中,有部分数据依赖同事开发的接口,需要等到同事的接口开发完成以后,才能开始我们的功能开发。这样依赖同事,有时候会影响到项目的上线节奏,所以我们在寻找有没能够解决这种场景的工具。
  2. 或者说,我们在分析完业务需求后,设计验证大的业务流程的代码的时候,也可以用到此类工具。首先把整体的大的处理逻辑完成,把细枝末节的处理打上标记 ToDo。
  3. Mockito 可以帮助我们在编写单元测试类的时候,调用指定接口返回指定的数据信息。通过模拟数据,替代真实数据,这样我们不需要等到同事的接口开发完成后,再开始我们的工作,做到“并行开发”。

二、实施过程

1. 引入依赖

		<!-- 引入mockito 依赖 -->
		<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. 一般使用项目中已经写好的测试基类即可。
@RunWith(SpringJUnit4ClassRunner.class)//表示整合JUnit4进行测试
@ContextConfiguration(locations={
        "classpath:spring/applicationContext.xml",
        "classpath:spring/applicationContext-persistent.xml"})//加载spring配置文件
public class BaseSpringTest {
    private Gson gson = new GsonBuilder().setPrettyPrinting().create();
    
    /**
     * 控制台打印展示对象 Json 字符串信息
     */
    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 测试类

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;

/**
 * demo
 */
public class MockitoBusinessTypeTest extends BaseMockitoSpringTest {
    @InjectMocks
    private BusinessTypeService businessTypeService = mock(BusinessTypeService.class);
    @Mock
    private IvrDealerSettingService ivrDealerSettingService;

    /**
     * 上边使用 InjectMocks 注释的类,里面如果有依赖到的 service 可以通过下面的方法,
     * Mock 注解,会自动为上边那个类注入下面这个service
     */
    @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);
    }

    /**
     * mock with autowired
     */
    @Test
    public void seatQueryTest(){
        String businessType = businessTypeService.getUserDefaultBusinessType("system").getCode();
        // 结果是可以正常执行的
        printlnJson(seatService.selectByBusinessType(businessType));
    }
}


5. Controller 对应的 Mockito 测试类

/**
 * Controller 测试类
 */
@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 {
        // mock Service 方法行为
        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:

  1. RunWith(SpringJUnit4ClassRunner.class): 表示使用Spring Test组件进行单元测试;
  2. WebAppConfiguration: 使用这个Annotate会在跑单元测试的时候真实的启一个web服务,然后开始调用Controller的Rest API,待单元测试跑完之后再将web服务停掉;
  3. ContextConfiguration: 指定Bean的配置文件信息,可以有多种方式,这个例子使用的是文件路径形式,如果有多个配置文件,可以将括号中的信息配置为一个字符串数组来表示;

然后是Mockito的Annotate:

  1. Mock: 如果该对象需要mock,则加上此Annotate;
  2. InjectMocks: 使mock对象的使用类可以注入mock对象,在上面这个例子中,mock对象是UserService,使用了UserService的是UserController,所以在Controller加上该Annotate;

Setup方法:

  1. MockitoAnnotations.initMocks(this): 将打上Mockito标签的对象起作用,使得Mock的类被Mock,使用了Mock对象的类自动与Mock对象关联。
  2. mockMvc: 细心的朋友应该注意到了这个对象,这个对象是Controller单元测试的关键,它的初始化也是在setup方法里面。

测试用例:

  1. mockMvc.perform: 发起一个http请求。
  2. post(url): 表示一个post请求,url对应的是Controller中被测方法的Rest url。
  3. param(key, value): 表示一个request parameter,方法参数是key和value。
  4. andDo(print()): 表示打印出request和response的详细信息,便于调试。
  5. andExpect(status().isOk()): 表示期望返回的Response Status是200。
  6. andExpect(content().string(is(expectstring)): 表示期望返回的Response Body内容是期望的字符串。

三、额外

Java 单元测试 PowerMock
文章对 PowerMock 做了非常详尽的讲解,非常推荐。
PowerMock也是依赖 Mockito,所以我认为这两个框架用起来的差别应该不大。

原文地址:http://www.cnblogs.com/lancediarmuid/p/16850807.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性