当前位置: 首页 > news >正文

Java后端测试

一、单元测试

1.1 单元测试基础概念

单元测试是针对软件中最小可测试单元(通常是方法或类)进行检查和验证的过程。在Java后端开发中,我们主要测试Service层的业务逻辑。

为什么需要单元测试?

  • 早期发现代码缺陷

  • 确保代码修改不会破坏现有功能

  • 作为代码文档,展示如何使用被测试代码

  • 促进更好的代码设计(可测试的代码通常结构更好)

常见测试类结构是怎样的?

是的,标准做法是每个业务类(如 UserService),对应一个测试类:

src/
├─ main/
│  └─ java/com/example/service/UserService.java
└─ test/└─ java/com/example/service/UserServiceTest.java

在测试类中,你:

  • 创建 @Mock 依赖(如 Mapper)

  • 创建 @InjectMocks 的 Service

  • 编写 @Test 方法,使用断言验证行为

1.2 单元测试完整依赖

如果你使用的是 Spring Boot,可以选择直接引入:

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

这个 starter 已经集成了 JUnit5 + Mockito + AssertJ 等测试依赖,适合大部分项目。

<!-- JUnit5 -->
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.9.3</version><scope>test</scope>
</dependency><!-- Mockito 核心 -->
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>5.8.0</version><scope>test</scope>
</dependency><!-- Mockito + JUnit5 集成支持(必须加上) -->
<dependency><groupId>org.mockito</groupId><artifactId>mockito-junit-jupiter</artifactId><version>5.8.0</version><scope>test</scope>
</dependency>

mockito-junit-jupiter 这个依赖是为了让你可以使用@ExtendWith(MockitoExtension.class) 与 JUnit5 配合。 如果不使用这种方式就要通过下面这种方式告诉 Mockito“请扫描当前类中的 @Mock、@InjectMocks 注解,创建 Mock 对象并注入”。

@BeforeEach
void setUp() {MockitoAnnotations.openMocks(this);
}

1.3 JUnit5 基本用法

1.3.1 基本注解

  • @Test: 标记一个方法为测试方法

  • @BeforeEach: 在每个测试方法前执行

  • @AfterEach: 在每个测试方法后执行

  • @BeforeAll: 在所有测试方法前执行(静态方法)

  • @AfterAll: 在所有测试方法后执行(静态方法)

  • @DisplayName: 为测试类或方法指定显示名称

(1)@BeforeAll 和 @AfterAll

执行时机

  • @BeforeAll:在整个测试类的所有测试方法执行之前运行一次

  • @AfterAll:在整个测试类的所有测试方法执行之后运行一次

静态方法要求:这两个注解标记的方法必须是static,因为它们在类级别执行,不依赖于任何测试实例

典型用途

class DatabaseTest {static Connection connection;@BeforeAllstatic void initDatabase() {connection = Database.connect(); // 所有测试共享的昂贵资源}@AfterAllstatic void closeDatabase() {connection.close(); // 所有测试完成后清理资源}@Test void testQuery1() { /* 使用connection */ }@Test void testQuery2() { /* 使用connection */ }
}

(2)@BeforeEach 和 @AfterEach

执行时机

  • @BeforeEach:在每个测试方法执行之前运行

  • @AfterEach:在每个测试方法执行之后运行

非静态方法:不需要static修饰

典型用途

class CalculatorTest {Calculator calculator;@BeforeEachvoid init() {calculator = new Calculator(); // 每个测试前创建新实例}@AfterEachvoid cleanup() {calculator.reset(); // 每个测试后清理状态}@Test void testAdd() { /* 使用新实例 */ }@Test void testSubtract() { /* 使用新实例 */ }
}

1.3.2 常用断言方法

定义:

断言(assert)是用于判断实际结果是否符合预期结果的“测试判断语句”。

  • 如果断言成功:测试通过 ✅

  • 如果断言失败:测试失败 ❌(会抛出 AssertionFailedError)

为什么断言很重要?

  • 没有断言,只是“运行代码”;

  • 有了断言,才能“验证结果是否正确”。

方法用途示例
assertEquals验证预期值=实际值assertEquals(10, result)
assertTrue验证条件为真assertTrue(list.isEmpty())
assertFalse验证条件为假assertFalse(user.isActive())
assertNull验证对象为nullassertNull(error)
assertNotNull验证对象非nullassertNotNull(response)
assertThrows验证是否抛出指定异常assertThrows(IllegalArgumentException.class, () → service.method(null))
assertAll分组执行多个断言assertAll("用户属性", () → assertEquals("John", user.name), () → assertEquals(30, user.age))

1.3.3 示例:测试工具类方法

class DateUtilsTest {@Test@DisplayName("测试日期格式化")void testFormatDate() {LocalDate date = LocalDate.of(2023, 5, 15);String expected = "2023-05-15";assertEquals(expected, DateUtils.formatDate(date));}@Test@DisplayName("测试解析日期")void testParseDate() {String dateStr = "2023-05-15";LocalDate expected = LocalDate.of(2023, 5, 15);assertEquals(expected, DateUtils.parseDate(dateStr));}@Test@DisplayName("测试非法日期格式")void testInvalidDateFormat() {assertThrows(DateTimeParseException.class, () -> {DateUtils.parseDate("2023/05/15");});}@Test@DisplayName("测试计算日期差")void testDaysBetween() {LocalDate start = LocalDate.of(2023, 5, 10);LocalDate end = LocalDate.of(2023, 5, 15);assertEquals(5, DateUtils.daysBetween(start, end));}
}class ValidationUtilsTest {@Test@DisplayName("测试邮箱验证")void testEmailValidation() {assertAll("邮箱验证测试",() -> assertTrue(ValidationUtils.isValidEmail("test@example.com")),() -> assertTrue(ValidationUtils.isValidEmail("user.name+tag@domain.co")),() -> assertFalse(ValidationUtils.isValidEmail("invalid.email")),() -> assertFalse(ValidationUtils.isValidEmail("user@.com")),() -> assertFalse(ValidationUtils.isValidEmail(null)));}@Test@DisplayName("测试手机号验证")void testPhoneValidation() {assertAll("手机号验证测试",() -> assertTrue(ValidationUtils.isValidPhone("13812345678")),() -> assertFalse(ValidationUtils.isValidPhone("12345678")),() -> assertFalse(ValidationUtils.isValidPhone("138123456789")),() -> assertFalse(ValidationUtils.isValidPhone("abc12345678")));}
}

1.4 Mockito 使用

Mockito是一个流行的Mock框架,用于创建和配置模拟对象,特别适合测试Service层时模拟Repository/DAO层。

核心概念

  • Mock对象:模拟真实对象的替代品,可以预设行为和返回值

  • Spy对象:部分模拟,对未存根的方法调用真实方法

1.4.1 常用注解

  • @Mock: 创建模拟对象

  • @InjectMocks: 创建实例并自动注入@Mock或@Spy字段

  • @Spy: 创建spy对象

一个测试类中是否只能有一个 @InjectMocks

不是只能有一个,但你必须明确每个要注入的对象,并且不能存在注入冲突。

🟡 多个 @InjectMocks 是可以的,只要不冲突!

@InjectMocks
private UserServiceImpl userService;@InjectMocks
private OrderServiceImpl orderService;

这种写法是允许的,只要这些 service 所依赖的 mock 字段(@Mock)都能被唯一地识别出来。

❌ 什么时候会冲突?

如果两个 @InjectMocks 修饰的类依赖的是同一个 @Mock,但你没有明确区分,那么 Mockito 就无法判断该把 mock 注入给哪个对象,会报错或行为混乱。

1.4.2 常用方法

表示 when(...) 里的内容 必须是“对 mock 对象的方法调用”而不能是静态方法。

如果想在中调用一个静态方法,必须按照以下方式:

你需要用 try (MockedStatic<...> ignored = ...) 的方式包装调用:

✅ 示例:mock SecurityContextUtil.getUserId()

import org.mockito.MockedStatic;
import org.mockito.Mockito;@Test
public void testAddFavorite() {Long userId = 1L;Long resourceId = 1L;// Mock 静态方法try (MockedStatic<SecurityContextUtil> mockedStatic = Mockito.mockStatic(SecurityContextUtil.class)) {mockedStatic.when(SecurityContextUtil::getUserId).thenReturn(userId);when(resourceService.checkResourceExist(resourceId)).thenReturn(true);when(redisTemplate.execute(any(), any(), any(), any())).thenReturn(1L);Boolean result = resourceFavoriteService.addFavorite(resourceId);assertTrue(result);}
}

⚠ 注意事项

  • mockStatic(...) 返回的是 MockedStatic<T> 类型,必须用 try-with-resources 包裹,否则 mock 不生效;

  • 静态方法的 mock 是线程隔离的,只在 try 块中有效;

  • 不支持 mock final 类或 native 方法;

  • IDE(如 IntelliJ IDEA)有时不能正确识别静态 mock,需要你加 mockito-inline 依赖;

(1)when(...).thenReturn(...) —— 模拟方法返回值

作用:告诉 Mockito:“当这个 mock 对象调用某个方法时,请返回我指定的值”。

默认情况下,mock 对象的方法如果没有用 when(...).thenReturn(...) 指定行为,会返回该方法返回类型的默认值

方法返回类型默认返回值
booleanfalse
int0
Objectnull
List空列表/null

示例:

when(userMapper.selectById(1L)).thenReturn(new User(1L, "张三"));

✅ 表示:如果测试代码中调用 userMapper.selectById(1L),就返回一个张三对象,而不会真的访问数据库。

在你自定义的被@Test注解的方法中,在调用包含userMapper.selectById(1L)的service层的方法之前使用这行代码,它会在你调用这个service层的方法中的userMapper.selectById(1L)时进行拦截返回对应的值。

@ExtendWith(MockitoExtension.class)
class UserServiceTest {@Mockprivate UserMapper userMapper;@InjectMocksprivate UserService userService;@Testvoid testGetUserName() {// 1. 准备阶段:告诉 mock 对象该如何响应when(userMapper.selectById(1L)).thenReturn(new User(1L, "张三"));// 2. 执行阶段:调用你要测试的业务方法String name = userService.getUserName(1L);// 3. 断言阶段:验证返回值assertEquals("张三", name);// 4.(可选)交互验证:确认底层 mapper 方法被调用verify(userMapper).selectById(1L);}
}

你在 when() 中指定的参数,要与被测代码调用 mock 方法时传入的参数值完全一致,否则不会命中,返回默认值(如 null、false、0)。

想匹配“任意参数”?用参数匹配器:anyXXX()

Mockito 提供了参数匹配器,比如:

匹配器方法说明
any()匹配任意类型对象
anyLong()匹配任意 long 值
anyInt()匹配任意 int 值
anyString()匹配任意字符串

🔁 多次调用返回不同结果:

when(service.getValue()).thenReturn("A").thenReturn("B");

第一次调用返回 A,第二次返回 B。


(2)verify(mock).method() —— 验证方法是否被调用

作用:测试代码是否“真的”调用了某个方法。

通常用于验证逻辑流程是否正确,如删除用户时是否调用了数据库删除方法。

示例:

userService.deleteUser(1L);
verify(userMapper).deleteById(1L); // 检查是否调用了 deleteById

(3)verify(mock, times(n)).method() —— 验证方法被调用 n 次

作用:在一些场景中我们期望某个方法被调用多次或指定次数,这时用 times(n)

示例:

userService.batchDelete(Arrays.asList(1L, 2L, 3L));
verify(userMapper, times(3)).deleteById(anyLong());

检查 userMapper.deleteById 是否被调用了 3 次。


(4)any(), anyString(), anyInt() 等参数匹配器

作用:用于设置/验证时对“任意参数”的匹配,不用写死具体参数。

避免你写死具体值,测试更灵活、健壮。

示例1:模拟返回值

when(userMapper.selectById(anyLong())).thenReturn(new User(1L, "默认用户"));

表示无论你传什么 long 类型参数,都返回这个默认用户。

示例2:验证方法调用

verify(userMapper).selectById(anyLong());

表示只要这个方法被调用,不管参数是什么,就算验证通过。


小总结:这些方法的配合使用

场景使用方法
设置 mock 返回值when(...).thenReturn(...)
验证方法是否调用verify(...)
验证调用次数verify(..., times(n))
模拟任意参数调用any(), anyString()

1.4.3 示例:UserService测试

在类上一定要使用@ExtendWith(MockitoExtension.class) 告诉 Mockito“请扫描当前类中的 @Mock、@InjectMocks 注解,创建 Mock 对象并注入”。

class UserServiceTest {@Mockprivate UserMapper userMapper;@InjectMocksprivate UserService userService;@Test@DisplayName("测试用户登录成功")void testLoginSuccess() {String username = "testUser";String password = "correctPassword";String encryptedPassword = PasswordUtil.encrypt(password);User mockUser = new User(1L, username, encryptedPassword);when(userMapper.findByUsername(username)).thenReturn(mockUser);User result = userService.login(username, password);assertNotNull(result);assertEquals(username, result.getUsername());verify(userMapper).findByUsername(username);}@Test@DisplayName("测试用户登录 - 密码错误")void testLoginWithWrongPassword() {String username = "testUser";String correctPassword = "correctPassword";String wrongPassword = "wrongPassword";User mockUser = new User(1L, username, PasswordUtil.encrypt(correctPassword));when(userMapper.findByUsername(username)).thenReturn(mockUser);assertThrows(AuthenticationException.class, () -> {userService.login(username, wrongPassword);});}@Test@DisplayName("测试用户登录 - 用户不存在")void testLoginWithNonExistentUser() {String username = "nonExistentUser";when(userMapper.findByUsername(username)).thenReturn(null);assertThrows(UserNotFoundException.class, () -> {userService.login(username, "anyPassword");});}
}

二、集成测试

2.1 集成测试 vs 单元测试的区别

维度单元测试(Unit Test)集成测试(Integration Test)
测试粒度测一个类(如 Service)测多个 Bean 的协作(如 Controller→Service→DB)
是否启动 Spring❌ 不启动容器✅ 启动整个 Spring Boot 容器
依赖注入方式@Mock + @InjectMocks@Autowired 注入真实 Bean
数据库连接无数据库 / Mock Mapper真实数据库(H2 或 MySQL)
测试类上注解@ExtendWith(MockitoExtension.class)@SpringBootTest

2.2 Testcontainers 原理与依赖

Testcontainers只是在替代本地运行的MySQL和Redis,并非是真实的生产环境(云端)

Testcontainers 启动的 MySQL/Redis 完全是 Docker 容器里跑的实例,和你机器上手动安装的服务 毫无关系

  • Docker 容器里的服务

    • 镜像(例如 mysql:8.0redis:7.0)被拉下来,作为一个隔离的进程组运行在 Docker 守护进程下。

    • 容器文件系统、网络端口、存储卷都与本地安装的服务隔离开来。

  • 本地安装的服务

    • 是你通过系统包管理(apt、yum、brew)或官方安装包安装的,运行在系统的服务管理器(systemd、launchctl)中。

因此,使用 Testcontainers 不会影响不会使用你本地安装的那套 MySQL/Redis;它会新建一个临时、独立、干净的容器环境,测试结束后再把容器删掉。

核心维度本地安装服务Testcontainers(推荐测试用)
✅ 测试隔离性多测试共享服务,易数据污染每次测试独立容器,自动清理
✅ 配置一致性手动配置,版本可能不一致配置写在测试代码中,团队一致
✅ 自动化/CI支持需手动安装服务,CI不友好自动拉镜像并启动,完美支持 CI 流程
✅ 启动与销毁启动慢、需清理启动快、测试后自动销毁
✅ 版本切换灵活性安装/切换麻烦一行代码换版本(如 mysql:8.0
  • CI = Continuous Integration(持续集成),指的是每次代码提交都自动触发构建和测试的流程。Testcontainers + CI 能保证在“无人值守”的环境里也能自动启动依赖服务并跑完测试。 
  • “代码即环境”:你在测试代码里声明要用 mysql:8.0redis:7.0 镜像,确保每个人本地和 CI 上用的都是同一个版本、同一套配置

  • Docker 化一致性:Testcontainers 启动的容器和你生产环境里跑的 Docker 容器环境极为相似,能更早地发现容器化部署时才会出现的问题;

  • 零运维负担:不需要在本机或 CI 节点预先安装 MySQL/Redis,只要 Docker 在,就能“一键启动、测试、销毁”。

2.2.1 原理

Testcontainers 是一个 Java 库,内部使用 Docker 启动临时容器,创建真实环境(MySQL、Redis、RabbitMQ等),用于测试。

  • 启动前自动拉取镜像并运行容器;

  • 容器启动后,通过 @DynamicPropertySource 将连接信息注入到 Spring;

  • 测试完成后,自动销毁容器,保持测试环境干净。

2.2.2 Testcontainers 不是 Spring Boot 的内置依赖,需要你单独添加。

组件依赖坐标必选
核心库org.testcontainers:testcontainers
JUnitorg.testcontainers:junit-jupiter
MySQLorg.testcontainers:mysql按需
Redisorg.testcontainers:redis按需
RabbitMQorg.testcontainers:rabbitmq按需
Kafkaorg.testcontainers:kafka按需
<!-- 基础测试依赖 -->
<dependency><groupId>org.testcontainers</groupId><artifactId>testcontainers</artifactId><version>1.16.3</version><scope>test</scope>
</dependency>
<dependency><groupId>org.testcontainers</groupId><artifactId>junit-jupiter</artifactId><version>1.16.3</version><scope>test</scope>
</dependency><!-- 按需添加模块 -->
<dependency><groupId>org.testcontainers</groupId><artifactId>mysql</artifactId><version>1.16.3</version><scope>test</scope>
</dependency>
<dependency><groupId>org.testcontainers</groupId><artifactId>redis</artifactId><version>1.16.3</version><scope>test</scope>
</dependency>
<dependency><groupId>org.testcontainers</groupId><artifactId>rabbitmq</artifactId><version>1.16.3</version><scope>test</scope>
</dependency>

2.2.3 基础使用方式

@Testcontainers
@SpringBootTest
class MyIntegrationTest {// 共享容器(所有测试方法共用)@Containerstatic final MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0");// 动态注入配置@DynamicPropertySourcestatic void registerProperties(DynamicPropertyRegistry registry) {registry.add("spring.datasource.url", mysql::getJdbcUrl);registry.add("spring.datasource.username", mysql::getUsername);registry.add("spring.datasource.password", mysql::getPassword);}@Testvoid testWithRealMySQL() {// 使用真实MySQL测试...}
}

2.2.4 复用代码结构方式

// 在src/test/java下创建
public abstract class BaseContainerTest {@Containerstatic final MySQLContainer<?> MYSQL = new MySQLContainer<>("mysql:8.0").withDatabaseName("test").withUsername("test").withPassword("test");@Containerstatic final RedisContainer REDIS = new RedisContainer("redis:6-alpine");@DynamicPropertySourcestatic void setupContainers(DynamicPropertyRegistry registry) {// 公共配置...}
}// 测试类继承即可
class MyTest extends BaseContainerTest {// 直接使用已启动的容器
}
  • 容器共享:使用static容器变量让所有测试方法共享同一容器

  • 基类封装:将容器配置放在抽象基类中复用

  • 资源清理

    @AfterEach
    void cleanup() {// 清理Redis数据redisTemplate.getConnectionFactory().getConnection().flushAll();
    }

 2.2.5 容器复用方法

在全局配置文件中打开复用:

~/.testcontainers.properties(推荐)

testcontainers.reuse.enable=true

或 src/test/resources/testcontainers.properties(也可以)

testcontainers.reuse.enable=true

表示你允许 Testcontainers 启动的容器复用(reuse),即:

  • 不会每次测试都销毁并重新启动 Redis/MySQL/RabbitMQ 容器;

  • 启动速度显著加快(节省 Docker 资源);

  • 尤其适合进行多线程/并发/性能测试。


✅ 开启复用后如何使用:

只要在容器定义时加 .withReuse(true)

@Container
static final MySQLContainer<?> MYSQL = new MySQLContainer<>("mysql:8.0").withReuse(true); // ✅ 显式声明允许复用@Container
static final RedisContainer REDIS = new RedisContainer("redis:6-alpine").withReuse(true);

✅ 如果不启用复用,会怎样?

  • 每次测试都会启动新容器(30s+延迟),不适合压力测试;

  • Docker 容器过多还可能残留占用资源;

  • 性能测试时每轮初始化都太慢,无法模拟真实高并发场景。

2.3 集成MySQL 

📄 文件结构:

src/
├─ main/
│  └─ resources/
│     └─ application.yml           <-- 正式环境配置(MySQL、Redis)
└─ test/└─ resources/└─ application-test.yml      <-- 测试环境配置(H2、内存配置)

2.3.1 使用 H2 内存数据库测试

✅ 所需依赖(Maven)

<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><version>2.2.224</version> <!-- 或使用稳定版 --><scope>runtime</scope>
</dependency>

使用 application-test.yml 专用于测试环境

# src/main/resources/application.yml(主配置)
spring:datasource:url: jdbc:mysql://localhost:3306/prod_dbdriver-class-name: com.mysql.cj.jdbc.Driverusername: prod_userpassword: ${DB_PASSWORD}
@SpringBootTest
@Transactional
@Rollback
@ActiveProfiles("test")
class UserMapperH2Test {@Autowiredprivate UserMapper userMapper;@Testvoid testInsert() {User user = new User();user.setUsername("h2User");user.setEmail("h2@example.com");int result = userMapper.insert(user);assertEquals(1, result);assertNotNull(user.getId());}
}

2.3.2 使用Testcontainers MySQL 数据库测试

# src/test/resources/application-test.yml(测试配置)
spring:datasource:url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQLdriver-class-name: org.h2.Driversql:init:schema-locations: classpath:schema.sqldata-locations: classpath:data.sqlh2:console:enabled: truepath: /h2-console

在集成测试类上统一加上:

@ActiveProfiles("test") // 明确使用测试环境配置 @SpringBootTest

如果不写默认加载 application-test.yml

@SpringBootTest
@Testcontainers
@ActiveProfiles("test")
class UserMapperMySQLTest {@Containerstatic MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0").withDatabaseName("test").withUsername("test").withPassword("test").withReuse(true);@DynamicPropertySourcestatic void configure(DynamicPropertyRegistry registry) {registry.add("spring.datasource.url", mysql::getJdbcUrl);registry.add("spring.datasource.username", mysql::getUsername);registry.add("spring.datasource.password", mysql::getPassword);registry.add("spring.datasource.driver-class-name", mysql::getDriverClassName);}@Autowiredprivate UserMapper userMapper;@Testvoid testInsert() {User user = new User();user.setUsername("tcUser");user.setEmail("tc@example.com");int result = userMapper.insert(user);assertEquals(1, result);assertNotNull(user.getId());}
}

@Rollback 注解作用:配合 @Transactional 使用,表示测试方法执行完成后回滚事务,不留任何数据痕迹

2.4 集成Redis

2.4.1 使用 Embedded Redis 测试

Embedded Redis依赖

<dependency><groupId>it.ozimov</groupId><artifactId>embedded-redis</artifactId><version>0.7.3</version><scope>test</scope>
</dependency>
特性Embedded Redis真实Redis/Testcontainers
启动速度快(进程内)较慢(需启动Docker)
功能完整性有限(非全功能实现)100%兼容
调试复杂度简单需要查看容器日志
适合场景简单操作验证需要完整Redis特性测试
@SpringBootTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class RedisEmbeddedTest {private RedisServer redisServer;@BeforeAllvoid startRedis() throws IOException {redisServer = new RedisServer(6379);redisServer.start();}@AfterAllvoid stopRedis() {redisServer.stop();}@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Testvoid testSetGet() {redisTemplate.opsForValue().set("key", "val");String value = redisTemplate.opsForValue().get("key");assertEquals("val", value);}
}

2.4.2 使用Testcontainers Redis 测试

@SpringBootTest
@Testcontainers
@ActiveProfiles("test")
class RedisTestcontainersTest {@Containerstatic GenericContainer<?> redis = new GenericContainer<>("redis:7.0").withExposedPorts(6379).withReuse(true);@DynamicPropertySourcestatic void configure(DynamicPropertyRegistry registry) {registry.add("spring.redis.host", redis::getHost);registry.add("spring.redis.port", () -> redis.getMappedPort(6379));}@Autowiredprivate RedisTemplate<String, String> redisTemplate;@Testvoid testRedisSetGet() {redisTemplate.opsForValue().set("k1", "v1");String value = redisTemplate.opsForValue().get("k1");assertEquals("v1", value);}
}

2.5 集成RabbitMQ

 2.5.1 混用云端 RabbitMQ 和Testcontainers MySQL/Redis

spring:rabbitmq:host: your-cloud-host.aliyuncs.comport: 5672username: yourUserpassword: yourPass

@Testcontainers
@SpringBootTest
@ActiveProfiles("test")
class MixedIntegrationTest {// ---- 容器化 MySQL ----@Containerstatic MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0").withReuse(true);// ---- 容器化 Redis ----@Containerstatic GenericContainer<?> redis = new GenericContainer<>("redis:7.0").withExposedPorts(6379).withReuse(true);@DynamicPropertySourcestatic void dynamicProperties(DynamicPropertyRegistry registry) {registry.add("spring.datasource.url", mysql::getJdbcUrl);registry.add("spring.datasource.username", mysql::getUsername);registry.add("spring.datasource.password", mysql::getPassword);registry.add("spring.redis.host", redis::getHost);registry.add("spring.redis.port", () -> redis.getMappedPort(6379));// 注意:rabbitmq 不在这里重写,还是用 application-test.yml 里的云端配置}@Autowiredprivate RabbitTemplate rabbitTemplate;    // 会连接到阿里云的 RabbitMQ@Testvoid testCloudRabbitAndLocalDb() {// 1)数据库操作走 Testcontainers 提供的 MySQL 容器// 2)缓存操作走 Testcontainers 提供的 Redis 容器// 3)消息操作走阿里云 RabbitMQObject msg = rabbitTemplate.receiveAndConvert("cloud.test.queue");// ...}
}
  • Testcontainers 只管理那些你用 @Container 启动的服务

  • 对于没有容器化声明的组件(如上例的 RabbitMQ),Spring 还是会按配置文件(application-test.ymlapplication.yml)去连接阿里云。

  • 这样就可以既用本地 Docker 容器来做数据库/缓存的隔离测试,又用云端实例来做消息队列的功能联调。

 2.5.2 使用 Testcontainers 启动 RabbitMQ 容器

import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.RabbitMQContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.DockerImageName;import static org.junit.jupiter.api.Assertions.assertEquals;@Testcontainers
@SpringBootTest
@ActiveProfiles("test")
class RabbitMQTestContainersTest {@Containerstatic final RabbitMQContainer rabbitMQ =new RabbitMQContainer(DockerImageName.parse("rabbitmq:3.10-management")).withExposedPorts(5672, 15672).withReuse(true);@DynamicPropertySourcestatic void rabbitProperties(DynamicPropertyRegistry registry) {registry.add("spring.rabbitmq.host", rabbitMQ::getHost);registry.add("spring.rabbitmq.port", rabbitMQ::getAmqpPort);registry.add("spring.rabbitmq.username", rabbitMQ::getAdminUsername);registry.add("spring.rabbitmq.password", rabbitMQ::getAdminPassword);}@Autowiredprivate RabbitTemplate rabbitTemplate;@Testvoid testSendAndReceive() {String queue = "tc.test.queue";// 在容器中声明队列rabbitTemplate.execute(channel -> {channel.queueDeclare(queue, false, false, false, null);return null;});// 发送并接收消息rabbitTemplate.convertAndSend(queue, "hello-tc");Object received = rabbitTemplate.receiveAndConvert(queue);assertEquals("hello-tc", received);}
}

文章转载自:

http://0JMdbL3J.ymyhg.cn
http://5SkYVPJ2.ymyhg.cn
http://AK1OnMpx.ymyhg.cn
http://EYwXsCKk.ymyhg.cn
http://gOtkU0Ws.ymyhg.cn
http://vDSQWCs8.ymyhg.cn
http://8BQbBu7N.ymyhg.cn
http://IVkwONhd.ymyhg.cn
http://za3SFtKA.ymyhg.cn
http://AkKUtL9v.ymyhg.cn
http://BbG7Y7jY.ymyhg.cn
http://L2DB3sin.ymyhg.cn
http://cOR1qnTC.ymyhg.cn
http://mMEQ0kFS.ymyhg.cn
http://sfsu8TCh.ymyhg.cn
http://Xlpm0kU1.ymyhg.cn
http://L2c9GAPS.ymyhg.cn
http://T31gm7aI.ymyhg.cn
http://HP5q5JfW.ymyhg.cn
http://Oe4MMXHO.ymyhg.cn
http://FMrVDDiK.ymyhg.cn
http://kbv03OSu.ymyhg.cn
http://YGOqx4GL.ymyhg.cn
http://FJp7eGc1.ymyhg.cn
http://gE1Y2j1p.ymyhg.cn
http://83gLjBlS.ymyhg.cn
http://4pLHAUcf.ymyhg.cn
http://Z5X5bnjf.ymyhg.cn
http://M6OuaaRn.ymyhg.cn
http://4eawWbhR.ymyhg.cn
http://www.dtcms.com/a/377858.html

相关文章:

  • Skywork-OR1:昆仑万维开源的数学代码推理系列模型
  • 【Linux】基本指令 · 上
  • OBS插件详细教程:OBS美颜插件下载,OBS美颜插件怎么用?
  • 如何在 Spring Boot 中指定不同的配置文件?
  • spring boot 拦截器增加语言信息
  • leedcode 算法刷题第三十二天
  • CentOS 7 下iscsi存储服务配置验证
  • 求解指定泛函的驻点所满足的偏微分方程及边界条件
  • 股指期货保证金一手需要多少钱?
  • LVS与Keepalived详解(一)负载均衡集群介绍
  • 【Proteus仿真】按键控制系列仿真——LED灯表示按键状态/按键控制LED灯/4*4矩阵键盘控制LED
  • 【前沿技术拓展Trip one】 芯片自动化和具身智能
  • javaEE之线程初步认识
  • `struct iovec`详解
  • python超市购物 2025年6月电子学会python编程等级考试一级真题答案解析
  • 项目模块划分
  • leetcode18(无重复字符的最长子串)
  • HackathonCTF: 1
  • redis cluster(去中心化)
  • 量子机器学习入门:三种数据编码方法对比与应用
  • 【Mysql】数据库的内置函数
  • 【Unity基础】枚举AudioType各个枚举项对应的音频文件类型
  • 2025数字化转型时代必备证书有哪些?
  • 认知-学习-时间管理系统模型-md说明文档
  • 如何用Postman做接口自动化测试
  • huggingface模型中各文件详解
  • cJson系列——json数据结构分析
  • Bandicam 班迪录屏 -高清录屏 多语便携版(Windows)
  • OpenLayers数据源集成 -- 章节五:MVT格式驱动的现代地图渲染引擎
  • 文件上传与诉讼资料关联表设计实战