楼层: 首页/ 软件技术/ Spring 核心框架/ Spring 测试:怎么验证 Bean 没画错
8

Spring 测试:怎么验证 Bean 没画错

Testing with Spring

脱离容器测试 Bean,就是 new 一个对象、手动 set 依赖——跟 Spring 没关系,等于白用。Spring 提供了测试支持:启动一个最小化的 Spring 容器跑测试,还能用 @MockBean 把真实依赖替换成 Mock。详细注解在 Spring Boot 那一章展开,这里先建立概念。

一个最小的 Spring 测试骨架

import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @SpringJUnitConfig(AppConfig.class) // 启动 Spring 容器跑测试 class UserServiceTest { @Autowired UserService userService; // 直接从容器里拿,不用自己 new @Test void greetShouldIncludeUserName() { String result = userService.greet(1); org.junit.jupiter.api.Assertions.assertTrue(result.contains("用户#1")); } }

给银行转账系统写测试:验证事务回滚

import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import static org.junit.jupiter.api.Assertions.*; @SpringJUnitConfig(BankApp.class) class TransferServiceTest { @Autowired TransferService transferService; @Autowired AccountDao accountDao; @Test void transferShouldRollbackWhenBalanceNotEnough() { accountDao.createAccount(1, 100); accountDao.createAccount(2, 0); // 转 500 出去,余额只有 100,应该抛异常并回滚 assertThrows(IllegalStateException.class, () -> transferService.transfer(1, 2, 500)); // 关键:回滚后 A 的余额应该还是 100,没被扣成负数 assertEquals(100.0, accountDao.balanceOf(1), 0.001); assertEquals(0.0, accountDao.balanceOf(2), 0.001); } }
测试通过 = 事务真的回滚了。 如果把 @Transactional 注释掉,这个测试会失败——因为扣钱不会被恢复。

Mockito:把真实依赖换成"假人"的三大件

测 UserService 时,它要调 UserDao 查数据库、还要调发短信服务。真连数据库、真发短信,测试又慢又脏。Mockito 就是造假对象(Mock):你让它"查 1 号用户就返回这个对象",它就照演,不碰真数据库。

能力人话
mock() / @Mock造一个假对象,所有方法默认返回空/null。
when(...).thenReturn(...)打桩:规定"调某个方法时返回啥"。
verify(...)验证:确认"某方法到底被调了几次、参数对不对"。
import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.InjectMocks; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; @ExtendWith(MockitoExtension.class) // 启用 Mockito class UserServiceTest { @Mock UserDao userDao; // 假 Dao,不连真库 @InjectMocks UserService userService; // 被测对象,自动注入上面的假 Dao @Test void getUserNameShouldReturnDaoName() { // 打桩:当 userDao.findById(1) 时,返回一个叫"小明"的用户 when(userDao.findById(1)) .thenReturn(new User(1, "小明")); String name = userService.getName(1); assertEquals("小明", name); // 验证:findById 确实被调了 1 次,参数是 1 verify(userDao, times(1)).findById(1); } }

论什么时候用 Mock,什么时候别用

用 Mock:依赖是数据库、远程接口、消息队列——慢、有副作用、不好造数据。别 Mock:自己写的纯工具类、值对象——直接 new 真对象更省心。Mock 是为了"隔离外部世界",不是为了把所有东西都假掉。

本章面试题 · 第 8 章(测试)

1.(理解题)Spring 单元测试里,@MockBean 和 @SpyBean 有什么区别?

查看答案

答案:@MockBean 把容器里的 Bean 整个换成假对象,方法调用全靠你 stub(when...thenReturn);@SpyBean 包一层"间谍",默认调真实方法,但你可以选择性地拦截某几个方法。解析:测 Service 时把它的 Dao 用 @MockBean 换成假的,就不用连真数据库。

2.(实践题)为什么测试方法上贴了 @Transactional,跑完数据会自动回滚?

查看答案

答案:Spring 测试框架默认给每个测试方法包一个事务,方法跑完自动 rollback,数据库保持干净,不会一个测试弄脏下一个。解析:想让测试真的写库,贴 @Commit 或 @Rollback(false)。