Spring Boot 基于 Mockito 单元测试

news2024/9/30 3:34:07

目录

前言

依赖与配置

pom

yml

演示代码

UserEntity

UserDao

UserService

EncryptUtil

单元测试

SpringBootTest

Mockito

PowerMock


前言

在网上刷到过“水货程序员”相关的帖子,列举了一些水货程序员的特征,其中一条就是不写单元测试,或者不知道单元测试是啥。看得瑟瑟发抖,完全不敢说话。

在小公司里当开发,对单元测试根本没有要求,测试也就是本地启动服务,自己调下接口看看是否调通,以及和前端本地联调。毕业后入行以来都没写过,想写也不知道该怎么做。自己想摆脱“水货程序员”标签去写单元测试,也只是照着网上博客,本地写一写,不知道写得是否规范,所以从没提交过单元测试代码。

后面跳槽,项目有要求写单元测试了,这就有能够参考的单元测试代码了。故记录下如何在Spring Boot 项目中写业务代码的单元测试代码。

依赖与配置

pom

    <properties>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
    </properties>   

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    <dependencies>

yml

server:
  port: 8888

spring:
  datasource:
    jdbc-url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
 

  jpa:
    show-sql: true
    properties:
      hibernate:
        hbm2ddl:
          auto: update
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect

演示代码

UserEntity

@Entity
@Table ( name = "user")
public class UserEntity {
    private Integer id;
    private String userName;
    private String password;


    @Id
    @GeneratedValue ( strategy = GenerationType.IDENTITY)
    @Column ( name = "id" )
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Basic
    @Column ( name = "username" )
    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    @Basic
    @Column ( name = "password" )
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

UserDao

@Repository
public interface UserDao extends JpaRepository<UserEntity, Integer> {

    boolean existByUserName(String userName);

}

UserService

简单的一个注册用户方法,若存在同名用户则抛出异常,否则加密密码,然后入库。

@Service
public class UserService {

    UserDao userDao;

    public Integer register(String userName, String password) {

        if (userDao.existByUserName(userName)) {
            throw new RuntimeException(String.format("当前用户名【%s】已经注册", userName));
        }

        UserEntity entity = new UserEntity();
        entity.setUserName(userName);
        password = EncryptUtil.encrypt(password);
        entity.setPassword(password);

        entity = userDao.save(entity);

        return entity.getId();
    }
}

EncryptUtil

加密工具类,只做演示,故简单返回一个字符串。

public class EncryptUtil {

    public static String encrypt(String source) {
        return String.format("%s-encrypt", source);
    }

    public static String decrypt(String source) {
        return String.format("%s-decrypt", source);
    }
}

单元测试

这里使用的 Junit 框架是 Junit4,测试类引用的 @Test 注解引用自 org.junit.Test 包下的。

与 Junit5 有所不同,Junit5 中的 @Test 是引用自 org.junit.jupiter.api.Test 

这里代码使用的 Spring Boot 版本为 2.3.7,相对较低,还集成了 junit,所以就使用 Junit4 来写单元测试。在更新的版本里就没有集成 junit,而是集成了 junit-jupiter。

如果使用了更高版本的 Spring Boot,下面代码的部分注解是没有的,比如 @RunWith

所以写单元测试代码还得根据对应的 Spring Boot 版本而定,不同版本注解的使用略有不同

SpringBootTest

不使用Mockito框架,依赖注入要测试的业务Service对象,然后直接调用测试的方法。这种写法在跑单元测试时就必须启动 Spring 容器,这样才能够注入依赖,否则单元测试中拿到的对象就会为null,运行中空指针异常,导致单元测试失败。

这种写法会出现一个很无语的场景,跑一个单元测试耗时0.1秒,但是启动Spring容器却要好几秒,如果项目很大,那么简单跑一个单元测试,绝大部分时间都花在启动容器上了。而且都启动容器了,那还不如本地直接启动服务用postman去调用接口方便。所以基本上没有这么去写单元测试。

//如果 @Test 引用自 org.junit.jupiter.api.Test
//则不需要 @RunWith 注解
@RunWith (SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

    @Autowired
    UserService userService;

    @Test
    public void registerTest() {
        String userName = "abc";
        String password = "123456";

        int id = userService.register(userName, password);

        Assert.assertNotNull("id为空", id);
    }

}

Mockito

Mockito 两个注解 @InjectMocks @Mock 使用区别如下:

  • 这里测试的是 UserService,就用 @InjectMocks 注解注入 UserService;
  • 被测试的类(UserService)中通过 @Autowired 注解注入的依赖,在测试类里面就用@Mock注解创建实例。UserService 依赖了 UserDao,故使用@Mock注解来注入UserDao;

让Mockito的注解生效,则需要在测试类上使用@RunWith注解,注解中value的值为 Runner,Runner其实就是各个框架在跑测试case的前后处理一些逻辑。mockito的MockitoJUnitRunner,作用就是在跑单测之前,将@Mock注解的对象构造出来。

这样就可以使用 mock 对象来写单元测试了,先定义 mock 对象的行为,然后调用被测试类的方法,最后验证返回结果是否符合预期。

严谨一点的话,可以将代码中的逻辑分支都写对应的单元测试。

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {

    @InjectMocks
    UserService userService;

    @Mock
    UserDao userDao;

    @Test
    public void registerSuccessTest() {
        String userName = "abc";
        String password = "123456";

        UserEntity user = new UserEntity();
        user.setId(1);
        user.setUserName(userName);
        user.setPassword(password);

        Mockito.when(userDao.existByUserName(userName)).thenReturn(false);
        Mockito.when(userDao.save(any())).thenReturn(user);

        Integer id = userService.register(userName, password);

        Assert.assertEquals("注册失败", 1, id.intValue());

        Assert.assertNotNull("id为空", id);
    }

    @Test
    public void registerFailTest() {
        String userName = "abc";
        String password = "123456";
        Mockito.when(userDao.existByUserName(any())).thenReturn(true);

        Assert.assertThrows(RuntimeException.class, () -> userService.register(userName, password));
    }
}

PowerMock

使用 Mockito 基本上就能写大部分单元测试了。但是在某些情况下可能无法满足特定的 Mock 需求,比如对static class, final class,constructor,private method等的mock操作。

在工作中,使用过数据脱敏的静态工具类,由于这个静态工具类注入了一个脱敏规则相关的对象。所以导致在单元测试中无法直接使用这个工具类,会报空指针错误。面对这种情况,就需要引入 PowerMock 框架来解决。

PowerMock 在 Mockito 的基础上扩展而来,支持 Mockito 的操作,也拓展了 static class, final class,constructor,private method等的mock操作。

pom 文件新增以下依赖

    <properties>
        <powermock.version>2.0.2</powermock.version>
    </properties>      
  
   <dependencies>
        <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-api-mockito2</artifactId>
            <version>${powermock.version}</version>
            <scope>test</scope>
        </dependency>
   </dependencies>

 单元测试代码

  • @RunWith(PowerMockRunner.class) 表示由 PowerMockRunner 去完成 mock 对象的创建
  • @PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class) 然后委托给 SpringJUnit4ClassRunner 去做依赖注入以及执行单元测试代码
  • @PrepareForTest({EncryptUtil.class}) PowerMock 去 mock【static class, final class,constructor,private method】时,需要将静态类写在 @prepareForTest 注解里
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
@PrepareForTest({EncryptUtil.class})
public class UserServiceTest {

    @InjectMocks
    UserService userService;

    @Mock
    UserDao userDao;

    @Test
    public void registerSuccessTest() {
        String userName = "abc";
        String password = "123456";

        UserEntity user = new UserEntity();
        user.setId(1);
        user.setUserName(userName);
        user.setPassword(password);

        PowerMockito.mockStatic(EncryptUtil.class);
        Mockito.when(EncryptUtil.encrypt(any())).thenReturn("password");

        Mockito.when(userDao.existByUserName(userName)).thenReturn(false);
        Mockito.when(userDao.save(any())).thenReturn(user);

        Integer id = userService.register(userName, password);

        Assert.assertEquals("注册失败", 1, id.intValue());
    }

    @Test
    public void registerFailTest() {
        String userName = "abc";
        String password = "123456";
        Mockito.when(userDao.existByUserName(any())).thenReturn(true);

        Assert.assertThrows(RuntimeException.class, () -> userService.register(userName, password));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2178493.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSDN 账号已经恢复,后续可能会把专栏内容整理成纸质书出版

笔者曾经系统地将自己在 ABAP&#xff0c;SAP UI5 和 SAP OData 开发领域的知识和经验&#xff0c;整理输出成四套开发教程&#xff1a; 零基础快速学习 ABAPSAP Fiori Elements 从入门到进阶SAP OData 开发实战教程 - 从入门到提高一套适合 SAP UI5 开发人员循序渐进的学习教…

Python酷玩之旅_如何连接MySQL(mysql-connector-python)

前言 Python作为数据科学、机器学习等领域的必选武器&#xff0c;备受各界人士的喜爱。当你面对不同类型、存储于各类介质的数据时&#xff0c;第一时间是不是要让它亮个相&#xff1f;做个统计&#xff0c;画个图表&#xff0c;搞个报表… 等等。 正如Java中的JdbcDriver一样…

以“棋”启智,乐在其中:二近制AI智能棋盘内含9种经典玩法让老人小孩爱不释手

近年来&#xff0c;人工智能算法被广泛地应用到生活的各个领域&#xff0c;棋类游戏亦是如此。各种搭载 A 智能算法的棋盘层出不穷&#xff0c;以“棋”启智&#xff0c;乐在其中成为了当下较流行地全民益智游戏之一。 棋类游戏为何屡受欢迎? 棋类游戏是一种能够激发民族智慧…

济南奇牛科技移动办公手机安全管理平台功能说明

济南奇牛信息科技有限公司自主研发的企业移动安全管理平台为企业提供一整移动终端安全解决方案&#xff0c;为解决企业在实施移动终端应用系统中会遇到的安全、应用管理和资产管理方面的问题&#xff0c;建立统一安全策略&#xff0c;解决企业数字化资产安全和员工隐私保护问题…

【教学类-23-02】20240929《不会写学号的中班幼儿的学号描字贴》(中2班描字)

背景需求&#xff1a; 今天给孩子们做中班操作材料包《练眼力》&#xff0c;希望他们在操作纸左上角写学号&#xff0c;结果有不少孩子嚷&#xff1a;“我不会写学号&#xff01;” “不会写的孩子举手&#xff0c;老师给你们做个字帖” 结果有不少孩子都举手了&#xff0c;我…

opencv学习:Harris角点检测和SIFT(尺度不变特征变换)算法完整代码实现

Harris角点检测 概念 Harris角点检测是一种在图像处理和计算机视觉领域广泛使用的技术&#xff0c;用于检测图像中的角点。角点是图像中两条边缘交点的位置&#xff0c;它们在图像分析、目标识别和图像配准等任务中非常重要。 角点&#xff1a;图像中的角点是指图像局部区域…

GDB :代码调试工具

文章目录 一、启动GDB二、GDB的基本命令1. 显示代码2. 运行程序3. 设置断点4. 单步执行5. 查看变量和内存6. 查看函数调用堆栈7. 修改变量值8. 退出GDB 一、启动GDB 在终端中&#xff0c;使用以下命令启动GDB并加载你的可执行文件&#xff1a; gdb ./your_program会进入以下界…

应用性能管理工具-SkyWalking

前言 随着微服务架构的流行&#xff0c;一次请求往往需要涉及到多个服务&#xff0c;因此服务性能监控和排查就变得更复杂&#xff0c;因此&#xff0c;就需要一些可以帮助理解系统行为、用于分析性能问题的工具&#xff0c;以便发生故障的时候&#xff0c;能够快速定位和解决…

关于大模型的10个思考

9月28日&#xff0c;第四届“青年科学家50论坛”在南方科技大学举行&#xff0c;美国国家工程院外籍院士沈向洋做了《通用人工智能时代&#xff0c;我们应该怎样思考大模型》的主题演讲&#xff0c;并给出了他对大模型的10个思考。 以下是他10个思考的具体内容&#xff1a; 1…

STM32移植RT-Thread实现DAC功能

在进行DAC的学习中&#xff0c;发现RT-Thread中没有该外设的驱动&#xff0c;因此需要自己进行相关配置 1.配置RT-Thread Setting中的DAC组件 2.在HAL库中完成DAC的配置(HAL库起到时钟的作用) 不懂HAL库配置的最好学一下HAL库的编程思想 3.在board.h中添加宏定义 我的RT-T…

关于分箱的一些介绍

在这篇文章中&#xff0c;我将介绍一种数据预处理的技术——分箱&#xff0c;然会将会从概念、步骤、分类、应用场景、注意事项与实际操作等方面去介绍它。 一、概念 分箱&#xff08;Binning&#xff09;是一种数据预处理技术&#xff0c;在数据分析和机器学习中经常使用。它…

Java8 用流收集数据之归约汇总

目录 规约汇总最大值 (max)・最小值 (min)统计总数 (count)统计求和 (summingInt・summingLong・summingDouble・sum&#xff09;平均值 (averagingInt・averagingLong・averagingInt・average&#xff09;统计梗概 (summarizingInt・summarizingLong・summarizingDouble・summ…

AI周报(9.22-9.28)

AI应用-Siipet宠物沟通师 Siipet是一款由SiiPet公司推出的创新宠物行为分析相机&#xff0c;旨在通过尖端技术加深宠物与主人之间的情感联系。这款相机利用先进的AI算法&#xff0c;能够自动识别和分析家中宠物的行为&#xff0c;并提供定制化的护理建议。 SiiPet相机的核心功…

益而益集团荣获2024年江苏省智能制造车间称号

近日&#xff0c;江苏省工信厅公示2024年江苏省智能制造车间名单&#xff0c;苏州益而益电器制造有限公司以其卓越的智能化转型成果&#xff0c;荣获2024年度江苏省级智能制造车间称号。 此次获评&#xff0c;是江苏省政府对益而益集团智能化高质量转型发展的认可及肯定&#…

活动在线报名小程序源码系统 自主提交表单+创建表单 带完整的安装代码包以及搭建部署教程

系统概述 随着各类活动的日益丰富和多样化&#xff0c;传统的报名方式逐渐显现出其局限性。纸质报名表格繁琐易错、人工统计费时费力&#xff0c;难以满足现代活动管理的需求。因此&#xff0c;开发一款集自主提交表单和创建表单功能于一体的活动在线报名小程序源码系统成为必…

mit6824-01-MapReduce详解

文章目录 MapReduce简述编程模型执行流程执行流程排序保证Combiner函数Master数据结构 容错性Worker故障Master故障 性能提升定制分区函数局部性执行缓慢的worker(slow workers) 常见问题总结回顾参考链接 MapReduce简述 MapReduce是一个在多台机器上并行计算大规模数据的软件架…

C++进阶知识2 多态

多态 1. 多态的概念2. 多态的定义及实现2.1 多态的构成条件2.1.2 虚函数2.1.3 虚函数的重写/覆盖2.1.5 虚函数重写的⼀些其他问题2.1.6 override和final关键字2.1.7 重载/重写/隐藏的对⽐ 3. 多态的原理3.2 多态的原理3.2.1 多态是如何实现的3.2.2 动态绑定与静态绑定3.2.3 虚函…

828华为云征文|部署在线文档应用程序 CodeX Docs

828华为云征文&#xff5c;部署在线文档应用程序 CodeX Docs 一、Flexus云服务器X实例介绍二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置2.4 Docker 环境搭建 三、Flexus云服务器X实例部署 CodeX Docs3.1 CodeX Docs 介绍3.2 CodeX Docs 部署3.3 CodeX…

SpringBoot整合JPA 基础使用

一、什么是JPA ‌‌1.JPA的定义和基本概念‌‌ ‌JPA&#xff08;Java Persistence API&#xff09;‌是Java中用于进行持久化操作的一种规范&#xff0c;它定义了一系列用于操作关系型数据库的API接口。通过这些接口&#xff0c;开发人员可以方便地进行数据库的增删改查等操…

ArcgisEngine开发中,Ifeatureclass.Addfield 报错0x80040655处理方法

1、ArcgisEngine开发中&#xff0c;Ifeatureclass.Addfield 报错0x80040655。如下图所示。 2、经分析&#xff0c;这是由于字段类型错误&#xff0c;经检查&#xff0c;是由于字段名为中文名&#xff0c;超出shp格式的最大字段长度量&#xff0c;看资料说是5个中文字符&#xf…