junit4 mockito powermockito 基本操作

参考资料

Mockito 系列教程

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

效果

image

image

posted @ 2024-06-22 12:58  潼关路边的一只野鬼  阅读(19)  评论(0编辑  收藏  举报