单元测试的重要性不言而喻,下文简要说明 JUnit5 测试中常用注解与实践方式。常用 IDE 都可自动生成基础测试类。
一、单元测试命名
可参考 7种流行的单元测试命名约定。
推荐采用 should...when... 命名方式,如 should_returnUser_when_queryValidId()。
但需注意:方法名不宜过长,影响可读性。
二、常用注解
1. 类级注解
@SpringBootTest:启动完整 Spring Boot 应用上下文,用于集成测试。一般用于 Controller 层或需要完整环境的测试。@ExtendWith(SpringExtension.class):JUnit5 替代 JUnit4 的@RunWith(SpringJUnit4ClassRunner.class),用于加载 Spring 上下文。@ContextConfiguration:手动指定加载的配置类或 XML 文件,常与@ExtendWith(SpringExtension.class)一起使用。@ExtendWith(MockitoExtension.class):用于纯 Mockito 测试,不依赖 Spring 容器。
2. Mock 相关注解
@Mock、@InjectMocks、@Spy:Mockito 提供。@Mock:创建 Mock 对象。@InjectMocks:创建对象实例并注入 Mock/Spy。@Spy:部分 Mock,会调用真实方法。
@MockBean、@SpyBean:Spring Boot 封装版本。@MockBean会将 Mock 对象注册进 Spring 容器。- 区别同上,
@SpyBean为部分 Mock。
3. 方法级注解
@Test:JUnit 标准测试方法注解。
4. 常用断言与验证
Mockito.when(...).thenReturn(...)Mockito.doNothing().when(...)Assertions.assertTrue(...)Assertions.assertNotNull(...)Assertions.assertThrows(...)Mockito.verify(bean, times(n)).method()
三、测试方法结构
推荐遵循 Given - When - Then 三段式结构:
- Given:准备数据或 Mock 行为;
- When:执行待测方法;
- Then:验证结果、断言逻辑。
四、数据库层单元测试
@MybatisPlusTest 仅加载 MyBatis Plus 相关 Bean,可避免 @SpringBootTest 启动完整环境。
来自 MyBatis Plus 3.4.0+ 的 mybatis-plus-boot-starter-test 模块。
示例
@MybatisPlusTest
@MapperScan({"com.example.demo.mapper"})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // 避免默认使用 H2,可改为 MySQL
@Sql(scripts = {"classpath:db/schema.sql", "classpath:db/data.sql"})
@Import(DataSourceConfig.class)
@ContextConfiguration(classes = {JunitDemo.class})
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void should_select5Items_when_afterInit() {
List<User> users = userMapper.selectList(null);
Assertions.assertEquals(5, users.size());
}
}
数据源配置
public class DataSourceConfig {
@Bean
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1");
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}
⚠️ 不加
@Configuration是为了防止被全局扫描影响其他测试。
如需观察数据库变化,可启动 H2 Server:
public class H2ServerTest {
private static Server h2Server;
public static void startH2Server() throws Exception {
h2Server = Server.createTcpServer("-tcp", "-tcpAllowOthers", "-tcpPort", "9092").start();
System.out.println("H2 server started on port 9092");
}
public static void stopH2Server() {
if (h2Server != null) {
h2Server.stop();
System.out.println("H2 server stopped");
}
}
}
五、Service 层单元测试
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserMapper userMapper;
@InjectMocks
private UserService userService;
@Test
void should_returnResult_when_queryById() {
// Given
long userId = 1;
User mockUser = new User();
mockUser.setId(userId);
mockUser.setName("li");
Mockito.doReturn(mockUser).when(userMapper).selectById(userId);
// When
User user = userService.getOnlyUser(userId);
// Then
Mockito.verify(userMapper, Mockito.times(1)).selectById(userId);
Assertions.assertEquals("li", user.getName());
}
@Test
void should_throwException_when_ageLessThan18() {
Mockito.doNothing().when(userMapper).insert(Mockito.isA(User.class));
Assertions.assertThrows(BusinessException.class,
() -> userService.insertUser(buildUserAgeLessThan18()));
}
}
部分场景,如果service要连接真实数据库,又不想启动整个项目,如下代码可以参考
@ExtendWith(SpringExtension.class)
//@ComponentScan(basePackages = {"com.example.demo.service"})
@ContextConfiguration(classes = {JunitDemo.class, DemoConfig.class}) // 告诉 Spring:测试环境要加载哪些配置类
@MybatisPlusTest
@MapperScan({"com.example.demo.mapper"})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // 避免默认使用 H2,可改为 MySQL
@Sql(scripts = {"classpath:db/schema.sql", "classpath:db/data.sql"}) // 需要执行的初始化Sql
@Import(DataSourceConfig.class) // 把某些类手动注册进当前 Spring 容器。
class UserServiceTest {
@SpyBean //默认执行真实默认,部分方法可mock
private UserMapper userMapper;
@Autowired
private UserService userService;
}
六、Controller 层集成测试
@SpringBootTest(classes = JunitDemo.class)
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void should_returnUser_when_query() throws Exception {
mockMvc.perform(
MockMvcRequestBuilders.get("/user/getOnlyUser?userId=1")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("name").value("xiaoming"))
.andExpect(MockMvcResultMatchers.jsonPath("age").value("18"));
}
}
七、注意事项
- 确保 JUnit 版本统一(全部使用 JUnit5 依赖)。
- 若遇到
unable to find a @SpringBootConfiguration,可手动在@SpringBootTest中指定classes。 @MybatisPlusTest默认使用内存数据库,如需连接真实库,请关闭自动替换数据源。
八、依赖示例
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter-test</artifactId>
<version>3.4.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>