单元测试
JUnit 5 + Mockito
Spring Boot 测试栈:
| 工具 | 作用 | 类比 PHP |
|---|---|---|
| JUnit 5 | 测试框架 | PHPUnit |
| Mockito | Mock 对象 | Mockery |
| AssertJ | 断言库(assertThat) | $this->assertEquals() |
| Spring Test | Spring 集成测试 | Laravel TestCase |
依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>加了这个依赖后,JUnit 5 + Mockito + AssertJ 都会自动引入。
单元测试(纯业务逻辑,不启动 Spring)
java
// src/test/java/com/example/myapp/service/UserServiceTest.java
class UserServiceTest {
private UserService userService;
private UserMapper userMapper;
@BeforeEach // 类比 setUp()
void setUp() {
userMapper = mock(UserMapper.class); // 类比 Mockery::mock()
userService = new UserService(userMapper); // 手动组装
}
@Test // 类比 @test 注解
void shouldFindUserById() { // 使用方法名描述测试意图
// given(准备数据)
User mockUser = new User();
mockUser.setId(1L);
mockUser.setName("Test");
when(userMapper.selectById(1L)).thenReturn(mockUser);
// when(执行)
User result = userService.findById(1L);
// then(断言)
assertThat(result.getName()).isEqualTo("Test"); // AssertJ 风格
verify(userMapper).selectById(1L); // 验证 Mapper 被调用
}
@Test
void shouldThrowWhenUserNotFound() {
when(userMapper.selectById(999L)).thenReturn(null);
assertThrows(ResourceNotFoundException.class, // 预期异常
() -> userService.findById(999L));
}
}切片测试(只启动部分 Spring 上下文)
Web 层测试
java
@WebMvcTest(UserController.class) // 只启动 UserController 的 Spring 上下文
class UserControllerTest {
@Autowired
private MockMvc mockMvc; // 模拟 HTTP 请求
@MockBean // Mock UserService,不加载真实 Bean
private UserService userService;
@Test
void shouldReturnUserList() throws Exception {
// 准备 Mock 数据
when(userService.findAll()).thenReturn(List.of(new User(1L, "Test")));
// 发送 GET 请求并断言
mockMvc.perform(get("/api/users")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("Test"));
// 类比 PHP:
// $response = $this->getJson('/api/users');
// $response->assertStatus(200)->assertJson([...]);
}
}数据层测试
java
@DataJpaTest // 只启动 JPA 相关组件
class UserRepositoryTest {
@Autowired
private UserMapper userMapper;
@Test
void shouldFindUserByEmail() {
User user = new User("test@example.com", "Test");
userMapper.insert(user);
User found = userMapper.selectOne(
new LambdaQueryWrapper<User>().eq(User::getEmail, "test@example.com"));
assertThat(found).isNotNull();
assertThat(found.getName()).isEqualTo("Test");
}
}全量集成测试(启动完整 Spring)
java
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) // 启动完整应用
class IntegrationTest {
@Autowired
private TestRestTemplate restTemplate; // 类比 $this->json()
@Test
void shouldCreateAndRetrieveUser() {
// POST 创建
var response = restTemplate.postForEntity(
"/api/users",
new CreateUserRequest("test@example.com", "Test"),
User.class
);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
Long id = response.getBody().getId();
// GET 查询
var getResponse = restTemplate.getForEntity(
"/api/users/{id}", User.class, id
);
assertThat(getResponse.getBody().getEmail())
.isEqualTo("test@example.com");
}
}常用断言
java
// AssertJ(推荐,链式调用)
assertThat(result.getName())
.isEqualTo("Test")
.isNotBlank()
.startsWith("T");
assertThat(resultList)
.hasSize(3)
.extracting(User::getName)
.contains("Alice", "Bob");
assertThat(exception.getMessage())
.contains("not found");
// JUnit 风格(传统)
assertEquals("Test", result.getName());
assertNotNull(result);
assertTrue(resultList.size() > 0);
assertThrows(ResourceNotFoundException.class, () -> userService.findById(999L));⚠️ 常见坑
1. 测试类不加 public
JUnit 5 允许包级私有(不加
public),推荐使用默认包级私有以减少代码。java@Test void shouldWork() { ... } // ✅ JUnit 5 支持
2. @SpringBootTest 启动很慢
每次
@SpringBootTest都会启动完整的 Spring 应用(3-10 秒)。尽量用@WebMvcTest、@DataJpaTest等切片测试,只启动必要组件。
3. @MockBean 和 @Autowired 的区别
@Autowired:注入真实的 Spring Bean@MockBean:用 Mockito Mock 替换掉真实的 Bean切片测试中,未涉及的 Bean 会自动 Mock(避免启动完整的上下文)。
4. 测试目录结构
src/ main/java/... # 业务代码 test/java/... # 测试代码(包路径与 main 保持一致)测试文件在
src/test/java/下,包路径和生产代码一致。不像 Laravel 的tests/Feature/和tests/Unit/。
5. 没有 RefreshDatabase Trait
Laravel 的
use RefreshDatabase在每个测试后回滚事务。Spring Boot 中用@Transactional实现:java@SpringBootTest @Transactional // 每个测试方法结束后自动回滚 class UserServiceTest { ... }但
@Transactional在@WebMvcTest中不适用(Web 层测试的上下文不同)。
6. 测试命名规范
java// 推荐:given_when_then 或 should_xxx 格式 @Test void givenValidEmail_whenCreateUser_thenSuccess() { ... } @Test void shouldThrowExceptionWhenEmailDuplicated() { ... }