文章目录
引入maven依赖(推荐用spring的方式)
spring-boot-starter-test已经自带JUnit5 + Mockito,不需要单独引入mockito。
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
单元测试类
java
// 1. 引入 JUnit5 和 Mockito 的扩展
@ExtendWith(MockitoExtension.class)
@DisplayName("用户服务 (UserService) 单元测试")
class UserServiceTest {
// 2. 创建虚拟的依赖对象(替身演员)
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
// 3. 创建被测试的真实对象,并自动注入 Mock 对象
@InjectMocks
private UserService userService;
// ================= 用户注册场景分组 =================
@Nested
@DisplayName("用户注册场景")
class RegisterUserTest {
@Test
@DisplayName("成功注册:保存数据并发送欢迎邮件")
void testRegisterUser_Success() {
// ========== Arrange (准备) ==========
String username = "张三";
User mockUser = new User(username);
when(userRepository.save(any(User.class))).thenReturn(mockUser);
doNothing().when(emailService).sendWelcomeEmail(any(User.class));
// ========== Act (执行) ==========
User result = userService.register(username);
// ========== Assert (断言) ==========
assertNotNull(result);
assertEquals("张三", result.getUsername());
verify(userRepository, times(1)).save(any(User.class));
verify(emailService).sendWelcomeEmail(any(User.class));
}
// 新增的测试方法:异常场景
@Test
@DisplayName("注册失败:用户名已存在时抛出异常")
void testRegisterUser_AlreadyExists() {
// ========== Arrange (准备) ==========
String username = "李四";
// 模拟数据库抛出用户名已存在的异常
when(userRepository.save(any(User.class)))
.thenThrow(new BusinessException("用户名已存在"));
// ========== Act & Assert (执行与断言) ==========
// 验证调用 register 方法时,确实抛出了预期的异常
BusinessException exception = assertThrows(
BusinessException.class,
() -> userService.register(username)
);
assertEquals("用户名已存在", exception.getMessage());
// 验证因为保存失败了,所以不应该触发发送邮件的逻辑
verify(emailService, never()).sendWelcomeEmail(any(User.class));
}
}
}
常用注解
@ExtendWith(MockitoExtension.class)
JUnit5专属。用于启用Mockito功能,自动初始化Mock对象并管理生命周期。(注:如果是老版本JUnit4,则使用@RunWith(MockitoJUnitRunner.class))
@Mock
创建一个依赖的"替身"(Mock对象),它的方法默认返回空值,需要通过when().thenReturn()来定义行为。
@InjectMocks
实例化你要测试的真实类(如UserService),并自动把带有@Mock注解的替身注入进去。
@Test
标记该方法为一个单元测试用例。
@DisplayName
为测试方法提供一个自定义的显示名称(支持中文),让测试报告更易读。
@Nested
@Nested的核心作用有3点:
1、逻辑分组(建文件夹)
把相关的测试用例(@Test)按业务场景分门别类,让测试报告呈现清晰的树状结构。
2、共享上下文(省代码)
内部类可以直接使用外部类的@Mock和@InjectMocks对象,不用重复写。
3、独立生命周期(互不干扰)
每个嵌套类可以有自己专属的@BeforeEach(初始化)和@AfterEach(清理)方法,互不影响。
@InjectMocks和@Mock区别
| 特性 | @Mock |
@InjectMocks |
|---|---|---|
| 作用 | 创建依赖的虚拟对象 | 创建被测类实例并注入依赖 |
| 是否需要打桩 | 是 | 否 |
| 是否调用真实逻辑 | 否(默认返回空值) | 是(调用真实方法,但依赖是 Mock) |
| 使用场景 | 隔离外部依赖 | 组装被测对象 |
| 是否必须配合使用 | 否(可单独使用) | 通常需要配合 @Mock 使用 |