junit4 mockito powermockito 基本操作
参考资料
maven单测生成覆盖率报告---Jacoco的使用
jacoco离线模式最佳实践,解决powermock覆盖率异常问题,包含排除包,类和检测覆盖率规则
Maven通过Jacoco生成单元测试覆盖率数据,无法生成jacoco.exec
jacoco代码覆盖率报告分析
jacoco代码覆盖率报告分析
maven multi-module(多模块)项目集成Jacoco代码覆盖率
Mockito与 PowerMock 版本对应关系
powermock框架(建议不用,mockito已经支持mock静态方法 )
一个 crud 例子
代码仓库
https://gitee.com/bzrj/thresh-boot
crud 内容讲解
这是一个字典类型的 crud , controller 为 com.laolang.thresh.module.system.controller.admin.dict.SysDictController
在这个 crud 中, controller 调用 logic , logic 调用 service 和 business, uml 图如下
---
title: 字典类型 crud
---
%%{init: {'theme':'neutral'}}%%
classDiagram
class SysDictController {
+ useLogic()
}
class SysDictLogic {
- sysDictTypeService
- sysDictTypeBusiness
}
class SysDictTypeBusiness {
- sysDictTypeService
}
class SysDictTypeService {
// Service methods
}
SysDictController ..> SysDictLogic
SysDictLogic ..> SysDictTypeService
SysDictLogic ..> SysDictTypeBusiness
SysDictTypeBusiness ..> SysDictTypeService
我们主要关心 logic 类,内容如下
package com.laolang.thresh.module.system.logic.admin.dict;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import com.laolang.thresh.framework.mybatis.core.BaseEntity;
import com.laolang.thresh.module.auth.api.AuthApi;
import com.laolang.thresh.module.system.api.dict.consts.entity.SysDictConsts;
import com.laolang.thresh.module.system.bizcode.SysDictBizCode;
import com.laolang.thresh.module.system.business.admin.dict.SysDictTypeBusiness;
import com.laolang.thresh.module.system.controller.admin.dict.req.SysDictTypeEditReq;
import com.laolang.thresh.module.system.controller.admin.dict.req.SysDictTypeListReq;
import com.laolang.thresh.module.system.controller.admin.dict.req.SysDictTypeSaveReq;
import com.laolang.thresh.module.system.controller.admin.dict.rsp.SysDictGroupListRsp;
import com.laolang.thresh.module.system.controller.admin.dict.rsp.SysDictListRsp;
import com.laolang.thresh.module.system.controller.admin.dict.rsp.SysDictTypeListRsp;
import com.laolang.thresh.module.system.exception.SystemBusinessException;
import com.laolang.thresh.module.system.persist.mysql.dict.condition.SelectDictListCondition;
import com.laolang.thresh.module.system.persist.mysql.dict.entity.SysDictType;
import com.laolang.thresh.module.system.persist.mysql.dict.service.SysDictDataService;
import com.laolang.thresh.module.system.persist.mysql.dict.service.SysDictTypeService;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@Slf4j
@RequiredArgsConstructor
@Service
public class SysDictLogic {
private final SysDictTypeService sysDictTypeService;
private final SysDictTypeBusiness sysDictTypeBusiness;
private final SysDictDataService sysDictDataService;
private final AuthApi authApi;
/**
* 根据字典名称模糊查询字典名称列表
*
* @param name 字典名称
*/
public List<String> typeNameList(String name) {
return sysDictTypeService.selectNameList(name);
}
/**
* 查询字典类型列表
*/
public SysDictTypeListRsp typeTypeList() {
List<String> types = sysDictTypeService.selectTypeList();
SysDictTypeListRsp rsp = new SysDictTypeListRsp();
if (CollUtil.isEmpty(types)) {
rsp.setTypes(Lists.newArrayList());
return rsp;
}
rsp.setTypes(
types.stream().map(s -> {
SysDictTypeListRsp.SysDictTypeBean bean = new SysDictTypeListRsp.SysDictTypeBean();
bean.setType(s);
bean.setDesc(SysDictConsts.Type.getByValue(s).getDesc());
return bean;
}).collect(Collectors.toList())
);
return rsp;
}
/**
* 查询字典分组列表
*/
public SysDictGroupListRsp typeGroupList() {
List<String> groups = sysDictTypeService.selectGroupList();
SysDictGroupListRsp rsp = new SysDictGroupListRsp();
if (CollUtil.isEmpty(groups)) {
rsp.setGroups(Lists.newArrayList());
return rsp;
}
rsp.setGroups(
groups.stream().map(s -> {
SysDictGroupListRsp.SysDictGroupBean bean = new SysDictGroupListRsp.SysDictGroupBean();
bean.setGroup(s);
bean.setDesc(SysDictConsts.GroupCode.getByValue(s).getDesc());
return bean;
}).collect(Collectors.toList())
);
return rsp;
}
/**
* 查询字典类型列表
*/
public SysDictListRsp typeList(SysDictTypeListReq req) {
SysDictListRsp rsp = new SysDictListRsp();
SelectDictListCondition condition = new SelectDictListCondition();
BeanUtils.copyProperties(req, condition);
PageInfo<SysDictType> entityPageInfo = sysDictTypeService.selectDictList(req.getPage(), req.getSize(), condition);
if (CollUtil.isEmpty(entityPageInfo.getList())) {
rsp.setPageInfo(new PageInfo<>());
return rsp;
}
List<SysDictListRsp.SysDictListRspBean> beanList = entityPageInfo.getList().stream().map(sysDictType -> {
SysDictListRsp.SysDictListRspBean bean = new SysDictListRsp.SysDictListRspBean();
BeanUtils.copyProperties(sysDictType, bean);
return bean;
}).collect(Collectors.toList());
PageInfo<SysDictListRsp.SysDictListRspBean> pageInfo = new PageInfo<>(beanList);
pageInfo.setPageNum(entityPageInfo.getPageNum());
pageInfo.setPages(entityPageInfo.getPageSize());
pageInfo.setTotal(entityPageInfo.getTotal());
pageInfo.setPages(entityPageInfo.getPages());
rsp.setPageInfo(pageInfo);
return rsp;
}
/**
* 编辑字典类型
*/
public void typeEdit(SysDictTypeEditReq req) {
SysDictType entity = sysDictTypeBusiness.assertExistById(req.getId());
if (!validUpdate(editReq2entity(req), true)) {
log.warn("字典类型信息已存在. req:{}", JSONUtil.toJsonStr(req));
throw new SystemBusinessException(SysDictBizCode.sys_dict_type_aready_exist);
}
updateEntityByEditReq(entity, req);
sysDictTypeService.updateById(entity);
}
private void updateEntityByEditReq(SysDictType entity, SysDictTypeEditReq req) {
entity.setName(req.getName());
entity.setType(req.getType());
entity.setGroupCode(req.getGroupCode());
entity.setStatus(req.getStatus());
entity.setRemark(req.getRemark());
entity.setUpdateBy(authApi.getAuthUser().getUsername());
}
/**
* 删除字典类型
*
* @param id 字典类型id
*/
public void typeDelete(Long id) {
SysDictType type = sysDictTypeBusiness.assertExistById(id);
long count = sysDictDataService.countByTypeAndGroupCode(type.getType(), type.getGroupCode());
if (count > 0) {
log.warn("字典类型【id:{} , type:{}, groupCode:{} , name:{}】下还有字典数据,不允许删除",
type.getId(), type.getType(), type.getGroupCode(), type.getName());
throw new SystemBusinessException(SysDictBizCode.sys_dict_type_has_data);
}
sysDictTypeService.removeById(id);
}
/**
* 添加字典类型
*/
public void typeSave(SysDictTypeSaveReq req) {
SysDictType insert = saveReq2entity(req);
if (!validUpdate(insert, false)) {
log.warn("字典类型信息已存在. req:{}", JSONUtil.toJsonStr(req));
throw new SystemBusinessException(SysDictBizCode.sys_dict_type_aready_exist);
}
insert.setCreateBy(authApi.getAuthUser().getUsername());
sysDictTypeService.save(insert);
}
private SysDictType editReq2entity(SysDictTypeEditReq req) {
SysDictType entity = new SysDictType();
entity.setId(req.getId());
entity.setName(req.getName());
entity.setType(req.getType());
entity.setGroupCode(req.getGroupCode());
return entity;
}
private SysDictType saveReq2entity(SysDictTypeSaveReq req) {
SysDictType entity = new SysDictType();
entity.setName(req.getName());
entity.setType(req.getType());
entity.setGroupCode(req.getGroupCode());
return entity;
}
/**
* 验证字典类型数据是否可以更新
*
* @param type 字典类型数据
* @param excludeId 是否排除入参中的 id
*/
private boolean validUpdate(SysDictType type, boolean excludeId) {
LambdaQueryWrapper<SysDictType> wrapper = Wrappers.lambdaQuery();
wrapper.eq(StrUtil.isNotBlank(type.getType()), SysDictType::getType, type.getType());
wrapper.eq(StrUtil.isNotBlank(type.getGroupCode()), SysDictType::getGroupCode, type.getGroupCode());
wrapper.ne(excludeId && Objects.nonNull(type.getId()), BaseEntity::getId, type.getId());
return 0 == sysDictTypeService.count(wrapper);
}
}
一个 junit4 mockito powermockito 的例子
添加依赖
<properties>
<mockito.version>3.1.0</mockito.version>
<junit.version>4.12</junit.version>
<powermock.version>2.0.9</powermock.version>
<jacoco-maven-plugin.version>0.8.1</jacoco-maven-plugin.version>
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
</properties>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-rule</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<!-- AuthApi 依赖 spring-curity -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jacoco</groupId>
<artifactId>org.jacoco.agent</artifactId>
<version>${jacoco-maven-plugin.version}</version>
<classifier>runtime</classifier>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-maven-plugin.version}</version>
<executions>
<execution>
<id>default-instrument</id>
<goals>
<goal>instrument</goal>
</goals>
</execution>
<execution>
<id>default-restore-instrumented-classes</id>
<goals>
<goal>restore-instrumented-classes</goal>
</goals>
</execution>
<execution>
<id>pre-test</id>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>post-test</id>
<phase>prepare-package</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${project.build.directory}/jacoco.exec</dataFile>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<argLine>-Dfile.encoding=UTF-8</argLine>
<testFailureIgnore>true</testFailureIgnore>
<systemPropertyVariables>
<jacoco-agent.destfile>${project.build.directory}/jacoco.exec</jacoco-agent.destfile>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
测试类
package com.laolang.thresh.module.system.logic.admin.dict;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.collect.Lists;
import com.laolang.thresh.module.auth.api.AuthApi;
import com.laolang.thresh.module.system.business.admin.dict.SysDictTypeBusiness;
import com.laolang.thresh.module.system.persist.mysql.dict.service.SysDictDataService;
import com.laolang.thresh.module.system.persist.mysql.dict.service.SysDictTypeService;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.security.core.Authentication;
@RunWith(PowerMockRunner.class)
@PrepareForTest({SysDictLogic.class, Authentication.class})
public class SysDictLogicTest {
@InjectMocks
SysDictLogic sysDictLogic;
@Mock
private SysDictTypeService sysDictTypeService;
@Mock
private SysDictTypeBusiness sysDictTypeBusiness;
@Mock
private SysDictDataService sysDictDataService;
@Mock
private AuthApi authApi;
@Test
public void typeNameListTest() {
SysDictLogic sysDictLogicSpy = PowerMockito.spy(sysDictLogic);
List<String> types = Lists.newArrayList("模块");
Mockito.when(sysDictTypeService.selectNameList(Mockito.anyString())).thenReturn(types);
List<String> results = sysDictLogicSpy.typeNameList("");
Assert.assertTrue(CollUtil.isNotEmpty(results));
Assert.assertTrue(StrUtil.isNotBlank(results.get(0)));
}
}
运行
mvn clean prepare-package