如何对springboot mapper 编写单元测试

如何对springboot mapper 编写单元测试

在 Spring Boot 中对 MyBatis Mapper 编写单元测试的核心目标是​​隔离真实数据库依赖​​,验证 SQL 逻辑与数据库交互的正确性。以下是完整的实践指南,涵盖环境配置、测试数据初始化、具体测试用例编写及常见问题解决。

​一、环境准备​

1. 依赖配置(pom.xml

需添加测试相关依赖,确保能启动 Spring 上下文、使用 H2 内存数据库,并集成 MyBatis:

<!-- Spring Boot 测试基础依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions>
</dependency><!-- H2 内存数据库(测试用) -->
<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>test</scope>
</dependency><!-- MyBatis 与 Spring Boot 集成(若项目未引入) -->
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.1</version> <!-- 按项目实际版本调整 -->
</dependency><!-- 断言库(可选,推荐 AssertJ) -->
<dependency><groupId>org.assertj</groupId><artifactId>assertj-core</artifactId><scope>test</scope>
</dependency>

​二、核心测试思路​

通过 ​​H2 内存数据库​​模拟真实数据库环境,测试时:

  1. 启动 Spring 上下文,加载 MyBatis Mapper 和 H2 数据源。

  2. 初始化 H2 表结构(通过 schema.sql)和测试数据(通过 data.sql或编程方式)。

  3. 注入 Mapper 接口,调用数据库操作方法,验证结果是否符合预期。

  4. 使用 @Transactional自动回滚测试数据,避免污染后续测试。

​三、具体实现步骤​

​1. 配置测试环境(application-test.properties)​

src/test/resources目录下创建测试专用的配置文件,指定 H2 数据库连接和 MyBatis 配置:

# 数据源配置(H2 内存数据库)
spring.datasource.url=jdbc:h2:mem:user_db;DB_CLOSE_DELAY=-1;MODE=MYSQL;INIT=CREATE SCHEMA IF NOT EXISTS user_db\;
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=# H2 控制台(可选,方便调试时查看数据)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console# MyBatis 配置(指定 Mapper XML 路径和实体别名包)
mybatis.mapper-locations=classpath:mapper/user/*.xml  # 假设 Mapper XML 放在 mapper/user 目录
mybatis.type-aliases-package=com.example.entity.user  # 实体类包路径# 打印 SQL 日志(调试用)
logging.level.com.example.mapper.user=debug
  • MODE=MYSQL:让 H2 模拟 MySQL 语法(如 AUTO_INCREMENTLIMIT),避免因数据库方言差异导致测试失败。

  • INIT=CREATE SCHEMA...:启动时自动创建数据库模式(可选,也可通过 schema.sql初始化)。

​2. 初始化测试数据​

测试前需准备数据库表和初始数据,推荐两种方式:

​方式 1:SQL 脚本(最常用)​

src/test/resources下创建 schema.sql(表结构)和 data.sql(测试数据):

-- schema.sql(建表语句,兼容 MySQL)
CREATE TABLE IF NOT EXISTS user (id INT PRIMARY KEY AUTO_INCREMENT,username VARCHAR(50) NOT NULL UNIQUE,age INT NOT NULL CHECK (age > 0),email VARCHAR(100),create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);-- data.sql(插入测试数据)
INSERT INTO user (username, age, email) VALUES ('张三', 20, 'zhangsan@example.com');
INSERT INTO user (username, age, email) VALUES ('李四', 25, 'lisi@example.com');
  • 注意:schema.sqldata.sql会在 Spring Boot 启动时自动执行(需确保 spring.sql.init.mode=always,默认已开启)。

​方式 2:编程初始化(灵活控制)​

通过 @Sql注解或 CommandLineRunner在测试启动时动态插入数据:

@SpringBootTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class UserMapperTest {@Autowiredprivate UserMapper userMapper;// 测试前插入额外数据(仅执行一次)@BeforeAllstatic void setupTestData(@Autowired DataSource dataSource) throws SQLException {try (Connection conn = dataSource.getConnection();Statement stmt = conn.createStatement()) {// 插入一条测试数据(ID 手动指定,避免与 data.sql 冲突)stmt.executeUpdate("INSERT INTO user (id, username, age, email) VALUES (99, '测试用户', 30, 'test@example.com')");}}
}
​3. 编写 Mapper 单元测试​

UserMapper为例(包含 selectByIdinsertupdatedelete等方法),测试类需覆盖核心场景。

​示例 1:查询操作测试​
import com.example.entity.user.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;import static org.assertj.core.api.Assertions.assertThat;@SpringBootTest // 加载 Spring 上下文(自动注入 Mapper)
@Transactional // 测试后自动回滚事务(关键!避免数据污染)
public class UserMapperTest {@Autowiredprivate UserMapper userMapper;// 测试:根据 ID 查询存在的用户@Testvoid selectById_ExistingUser_ReturnsUser() {// 准备:已知测试数据的 ID(data.sql 插入的第一条数据 ID 为 1)Integer userId = 1;// 执行:调用 Mapper 方法User user = userMapper.selectById(userId);// 断言:验证返回结果符合预期assertThat(user).isNotNull().hasFieldOrPropertyWithValue("username", "张三").hasFieldOrPropertyWithValue("age", 20).hasFieldOrPropertyWithValue("email", "zhangsan@example.com");}// 测试:根据 ID 查询不存在的用户@Testvoid selectById_NonExistingUser_ReturnsNull() {Integer invalidId = 999; // 不存在的 IDUser user = userMapper.selectById(invalidId);assertThat(user).isNull();}
}
​示例 2:插入操作测试​
@Test
void insert_NewUser_SavesToDatabase() {// 准备:新用户对象(ID 由数据库自增生成)User newUser = new User();newUser.setUsername("王五");newUser.setAge(28);newUser.setEmail("wangwu@example.com");// 执行:调用插入方法int affectedRows = userMapper.insert(newUser);// 断言 1:影响行数为 1(插入成功)assertThat(affectedRows).isEqualTo(1);// 断言 2:自增 ID 被正确赋值(需数据库支持自增主键)assertThat(newUser.getId()).isGreaterThan(0);// 断言 3:查询数据库验证数据已持久化User savedUser = userMapper.selectById(newUser.getId());assertThat(savedUser).isNotNull().hasFieldOrPropertyWithValue("username", "王五").hasFieldOrPropertyWithValue("age", 28);
}
​示例 3:更新操作测试​
@Test
void update_ExistingUser_ModifiesDatabase() {// 准备:已存在的用户 ID(data.sql 插入的第二条数据 ID 为 2)Integer userId = 2;User userToUpdate = new User();userToUpdate.setId(userId);userToUpdate.setAge(26); // 修改年龄userToUpdate.setEmail("lisi_updated@example.com"); // 修改邮箱// 执行:调用更新方法int affectedRows = userMapper.updateById(userToUpdate);// 断言 1:影响行数为 1(更新成功)assertThat(affectedRows).isEqualTo(1);// 断言 2:查询数据库验证字段已更新User updatedUser = userMapper.selectById(userId);assertThat(updatedUser).hasFieldOrPropertyWithValue("age", 26).hasFieldOrPropertyWithValue("email", "lisi_updated@example.com");
}
​示例 4:删除操作测试​
@Test
void delete_ExistingUser_RemovesFromDatabase() {// 准备:已存在的用户 ID(测试前插入的 ID 为 99)Integer userIdToDelete = 99;// 执行:调用删除方法int affectedRows = userMapper.deleteById(userIdToDelete);// 断言 1:影响行数为 1(删除成功)assertThat(affectedRows).isEqualTo(1);// 断言 2:查询数据库确认用户已删除User deletedUser = userMapper.selectById(userIdToDelete);assertThat(deletedUser).isNull();
}

​四、关键优化技巧​

1. 事务自动回滚(@Transactional
  • ​作用​​:测试方法执行后自动回滚事务,避免测试数据残留,保证测试独立性。

  • ​注意​​:若测试需要验证事务行为(如 @Transactional失效场景),可移除该注解,但需手动清理数据。

2. 轻量级测试(不加载 Spring 上下文)

若希望提升测试速度(避免 Spring 上下文启动耗时),可直接通过 MyBatis 的 SqlSessionFactory手动创建 Mapper 实例:

​步骤 1:配置 SqlSessionFactory(测试专用)​

src/test/resources下创建 mybatis-config-test.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><environments default="test"><environment id="test"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="org.h2.Driver"/><property name="url" value="jdbc:h2:mem:user_db;MODE=MYSQL;INIT=CREATE SCHEMA IF NOT EXISTS user_db"/><property name="username" value="sa"/><property name="password" value=""/></dataSource></environment></environments><mappers><package name="com.example.mapper.user"/> <!-- Mapper 接口包路径 --></mappers>
</configuration>
​步骤 2:编写轻量级测试类​
import com.example.entity.user.User;
import com.example.mapper.user.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;import java.io.IOException;
import java.io.InputStream;import static org.assertj.core.api.Assertions.assertThat;public class UserMapperLightweightTest {private static SqlSessionFactory sqlSessionFactory;@BeforeAllstatic void init() throws IOException {// 加载 MyBatis 测试配置String resource = "mybatis-config-test.xml";InputStream inputStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);}@Testvoid selectById_WithLightweight() {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// 假设已通过 SQL 脚本插入 ID=1 的用户User user = userMapper.selectById(1);assertThat(user).isNotNull();assertThat(user.getUsername()).isEqualTo("张三");}}
}
3. 调试技巧
  • ​查看 H2 控制台​​:访问 http://localhost:8080/h2-console(根据项目端口调整),输入 JDBC URL jdbc:h2:mem:user_db,可实时查看测试数据。

  • ​打印 SQL 日志​​:在 application-test.properties中配置 logging.level.com.example.mapper.user=debug,日志会输出实际执行的 SQL 和参数。

​五、常见问题解决​

1. Mapper 接口无法注入(NoSuchBeanDefinitionException
  • ​原因​​:Spring 未扫描到 Mapper 接口。

  • ​解决​​:

    • 在测试类或配置类上添加 @MapperScan("com.example.mapper.user")(指定 Mapper 接口包路径)。

    • 确认 Mapper 接口标注了 @Mapper注解(或在 mybatis-config-test.xml中通过 <mappers>标签注册)。

2. H2 数据库执行 SQL 报错(如语法不兼容)
  • ​原因​​:H2 默认模式与真实数据库(如 MySQL)语法差异(如 AUTO_INCREMENTvs AUTO_INCREMENT)。

  • ​解决​​:

    • 在 H2 连接 URL 中添加 MODE=MYSQL(或其他数据库模式,如 ORACLE)。

    • 检查 SQL 脚本,避免使用特定数据库特有的语法(如 MySQL 的 BACKUP TABLE)。

3. 测试数据未正确初始化(data.sql未执行)
  • ​原因​​:Spring Boot 未触发 data.sql执行。

  • ​解决​​:

    • 确认 data.sql位于 src/test/resources目录。

    • application-test.properties中添加 spring.sql.init.mode=always(强制初始化)。

​总结​

对 Spring Boot Mapper 编写单元测试的核心步骤:

  1. ​配置 H2 内存数据库​​:模拟真实数据库环境。

  2. ​初始化测试数据​​:通过 schema.sqldata.sql准备表结构和初始数据。

  3. ​编写测试用例​​:覆盖 CRUD 操作,验证结果正确性。

  4. ​事务回滚​​:使用 @Transactional保证测试独立性。

  5. ​轻量级优化​​(可选):通过手动创建 SqlSessionFactory提升测试速度。

通过以上方法,可高效验证 Mapper 层的数据库交互逻辑,确保代码质量。

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

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

相关文章

学习游戏制作记录(数据加密以及主菜单和画面优化)8.27

1.实现数据加密FileDataHandler 脚本&#xff1a;private bool encryptData false;//是否加密public string codeWord "alexdev";//加密码public FileDataHandler(string _dataDirPath, string _fileName, bool _encryptData){dataDirPath _dataDirPath;FileName …

五自由度磁悬浮轴承同频振动抑制:从机理拆解到传递函数验证的核心方案

摘要 五自由度磁悬浮轴承凭借无摩擦、高转速的优势,在航空航天、透平机械等领域应用广泛,但转子不平衡质量引发的同频振动(频率与转子转速一致)始终是制约其精度的核心痛点。本文从转子不平衡振动的物理机理出发,详细推导不平衡力的数学模型,分析位移输出中扰动信号的叠…

CSS 优先级:公司组织架构模型

为什么我的CSS样式不生效&#xff1f; 在网页开发中&#xff0c;你可能经常会遇到一个令人困惑的问题&#xff1a;你明明写了CSS代码&#xff0c;但是样式却不生效&#xff0c;或者出现了意想不到的冲突。你可能会反复检查代码&#xff0c;却找不到任何语法错误。这背后隐藏的原…

Go语言循环语句全解析

循环语句概述循环语句在编程中的作用循环语句是编程中控制程序流程的重要结构&#xff0c;它允许我们重复执行特定代码块&#xff0c;直到满足终止条件。在数据处理、算法实现、系统监控等场景中&#xff0c;循环都发挥着关键作用。典型应用场景&#xff1a;数据处理&#xff1…

基于NXP iMXRT600音频算法开发方法

iMXRT600 是一款高性能的微控制器&#xff0c;在开发音频算法时可按以下步骤和方法进行&#xff1a;1. 开发环境搭建硬件平台准备好 iMXRT600 开发板&#xff0c;确保开发板上具备音频输入输出接口&#xff0c;如 I2S&#xff08;Inter - IC Sound&#xff09;接口用于音频数据…

怎么理解API?

想象一下你去一家餐厅吃饭。你&#xff08;用户&#xff09;不会直接走进厨房告诉厨师怎么做菜&#xff0c;对吧&#xff1f;你会怎么做&#xff1f;你会拿起菜单&#xff0c;查看上面列出的菜品&#xff08;例如“意大利面”&#xff09;、它们的描述和价格。然后&#xff0c;…

系统架构设计师备考第7天——网络协议中间件软件构件

一、网络协议 核心概念 定义&#xff1a;网络协议是计算机通信的“语言规则”&#xff0c;规定了数据格式、传输时序、控制信号等&#xff0c;确保不同系统实体间正常通信。作用&#xff1a;实现资源共享与信息交换的基础。常见类型&#xff1a; 局域网协议&#xff08;LAN&…

《数据之心》

《数据之心》一、故障2045年&#xff0c;中国“天算”量子云中枢第七区。鱼小妖站在控制台前&#xff0c;指尖划过全息屏&#xff0c;蓝光映在她清秀的脸庞上。她的长发如墨&#xff0c;眸子却似星河&#xff0c;倒映着无数跳动的数据流。她是第七区最年轻的系统神经工程师&…

《C++ Primer 第五版》不要返回局部对象的引用或指针

1. 先看一个“看似合理”的例子#include <iostream> using namespace std;int& foo() {int x 10; // 局部变量&#xff0c;存在于栈中return x; // 返回它的引用 }int main() {int& ref foo(); // ref 绑定到了已经被销毁的 xcout << ref &…

2024鸿蒙样题需要掌握的知识点

一、读取json格式文件为对象或数组&#xff0c;显示相应字段1、创建json文件的参数一致的类2、导入类、导入json文件3、循环渲染import router from ohos.router //导入即对象 import books from resources/rawfile/book1.json import { Book } from ../model/BookEntry Compon…

QML Charts组件之坐标轴示例

目录引言&#x1f3af; 运行效果预览&#x1f4da; 相关系列文章五种坐标轴详解与代码实践1. 数值坐标轴&#xff08;ValueAxis&#xff09;示例代码说明2. 对数坐标轴&#xff08;LogValueAxis&#xff09;示例代码说明3. 日期坐标轴&#xff08;DateTimeAxis&#xff09;示例…

Vue3+ElementPlus倒计时示例

按钮文字默认显示“开始倒计时”当点击按钮时&#xff0c;显示正在倒计时(倒计时数字)倒计时结束按钮显示“开始倒计时” 倒计时逻辑 Hooks 函数 hooks/useCountDown.js /*** hooks函数&#xff1a;函数是用于封装和复用组件逻辑的一种机制* 定义&#xff1a;Hooks 是一种在不使…

docker 的网络

1.查看docker里面的网络docker network ls 2.查看某个 Docker 网络的 网关 IP 和 子网段docker network inspect <网络名或ID>

数据挖掘,到底是在挖掘什么?

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;CSDN博客专家   &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01…

【车载开发系列】CS+ for CC开发环境IDE

【车载开发系列】CS for CC开发环境IDE 【车载开发系列】CS for CC开发环境IDE【车载开发系列】CS for CC开发环境IDE一. 引言二. IDE安装三. 新建工程四. 堆与栈内存的设置1&#xff09;栈内存设置2&#xff09;堆内存设置一. 引言 瑞萨单片机开发环境有三种&#xff1a;estu…

如何将视频从安卓设备传输到Mac?

你是否想要创建备份、释放存储空间&#xff0c;或者分享难忘时刻&#xff1f;你可能想要轻松地将视频从安卓设备复制到MacBook。在本篇关于“如何将视频从安卓传输到Mac”的指南中&#xff0c;我们将介绍五种智能方法&#xff0c;帮助你无缝地复制视频。从传统的WiFi连接方法到…

MyBatis 初识:框架定位与核心原理——SQL 自由掌控的艺术

&#x1f50d; MyBatis 初识&#xff1a;框架定位与核心原理——SQL 自由掌控的艺术 文章目录&#x1f50d; MyBatis 初识&#xff1a;框架定位与核心原理——SQL 自由掌控的艺术&#x1f9e9; 一、为什么需要 ORM 框架&#xff1f;&#x1f4a1; JDBC 的痛点&#xff1a;原始时…

谷粒商城项目-P6环境-使用vagrant快速创建Linux虚拟机

1.虚拟机 虚拟机使用virtualbox,我使用的是6.0.12版本 2.创建linux系统 使用vagrant 验证是否安装完成vagrant 在cmd使用vagrant看看有没有命令提示 创建虚拟机 在cmd使用vagrant init centos/7创建 创建完成后使用vagrant up启动虚拟容器 启动完成后&#xff0c;使用va…

人形机器人的“奥运会“:宇树科技领跑,动捕技术成训练关键

近年来&#xff0c;人工智能、仿生学和运动控制技术的突破性发展&#xff0c;正推动人形机器人成为全球科技竞争的新焦点。各国政府、科研机构和企业加速布局医疗、救援、服务等领域的应用场景。在此背景下&#xff0c;首届世界人形机器人大会应运而生并于近日圆满落幕。此次运…

20250823给荣品RD-RK3588开发板刷Rockchip原厂的Android14【EVB7的V10】时调通AP6275P的WIFI

20250823给荣品RD-RK3588开发板刷Rockchip原厂的Android14【EVB7的V10】时调通AP6275P的WIFI 2025/8/23 17:02【我是先将这个DTSI文件中的代码块直接搬到rk3588-evb7-lp4.dtsi中&#xff0c;然后就可以上网了。接着就微调到最终版本&#xff01;】 E:\RD-RK3588_Android13\kern…