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)。