Spring Boot单元测试的Mock
下面是一个Spring Boot单元测试的Mock示例,使用Mockito框架模拟依赖关系,不启动整个Spring容器(轻量级测试):
示例场景
UserService:业务服务类UserRepository:数据访问层(被Mock的对象)- 测试目标:
UserService的业务逻辑
代码实现
1. 实体类
// src/main/java/com/example/demo/model/User.java
public class User {
private Long id;
private String name;
private String email;
// 构造方法/getters/setters
}
2. Repository接口
// src/main/java/com/example/demo/repository/UserRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByEmail(String email);
}
3. Service实现类(无接口)
// src/main/java/com/example/demo/service/UserService.java
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional
public User createUser(String name, String email) {
if (userRepository.existsByEmail(email)) {
throw new IllegalArgumentException("Email already exists");
}
User user = new User();
user.setName(name);
user.setEmail(email);
return userRepository.save(user);
}
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
}
4. 单元测试类
// src/test/java/com/example/demo/service/UserServiceTest.java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class) // 启用Mockito
class UserServiceTest {
@Mock
private UserRepository userRepository; // 被Mock的依赖
@InjectMocks
private UserService userService; // 被测试的服务,自动注入Mock对象
@Test
void createUser_Success() {
// 1. 准备测试数据
String name = "John";
String email = "john@example.com";
User savedUser = new User(1L, name, email);
// 2. 定义Mock行为
when(userRepository.existsByEmail(email)).thenReturn(false);
when(userRepository.save(any(User.class))).thenReturn(savedUser);
// 3. 执行测试方法
User result = userService.createUser(name, email);
// 4. 验证结果
assertNotNull(result);
assertEquals(1L, result.getId());
assertEquals(email, result.getEmail());
// 5. 验证Mock交互
verify(userRepository).existsByEmail(email);
verify(userRepository).save(any(User.class));
}
@Test
void createUser_WhenEmailExists_ShouldThrowException() {
// 准备测试数据
String email = "duplicate@example.com";
// 定义Mock行为
when(userRepository.existsByEmail(email)).thenReturn(true);
// 执行并验证异常
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> userService.createUser("Alice", email)
);
assertEquals("Email already exists", exception.getMessage());
verify(userRepository, never()).save(any());
}
@Test
void getUserById_Found() {
// 准备测试数据
Long userId = 1L;
User mockUser = new User(userId, "Bob", "bob@example.com");
// 定义Mock行为
when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser));
// 执行测试方法
User result = userService.getUserById(userId);
// 验证结果
assertEquals(userId, result.getId());
assertEquals("Bob", result.getName());
}
@Test
void getUserById_NotFound() {
// 准备测试数据
Long userId = 999L;
// 定义Mock行为
when(userRepository.findById(userId)).thenReturn(Optional.empty());
// 执行并验证异常
RuntimeException exception = assertThrows(
RuntimeException.class,
() -> userService.getUserById(userId)
);
assertEquals("User not found", exception.getMessage());
}
}
关键点解析
-
测试框架:
@ExtendWith(MockitoExtension.class):启用Mockito- 不需要
@SpringBootTest,不启动Spring容器
-
Mock对象:
@Mock:创建模拟依赖(如UserRepository)@InjectMocks:创建被测对象并注入模拟依赖
-
Mock行为配置:
when(mock.method()).thenReturn(value):定义方法返回值when(mock.method()).thenThrow(exception):定义方法抛出异常
-
验证交互:
verify(mock).method():验证方法被调用verify(mock, times(n)).method():验证调用次数verify(mock, never()).method():验证从未调用
-
断言:
assertEquals:验证值相等assertThrows:验证抛出预期异常assertNotNull:验证非空
优势
- 超快执行速度:不启动Spring容器,测试在毫秒级完成
- 精准测试:只测试当前类的业务逻辑
- 完全控制:可以模拟各种边界情况和异常场景
- 无副作用:不操作真实数据库,无数据污染
运行测试
在IDE中直接运行测试类,或使用Maven命令:
mvn test
最佳实践:对于Service层的业务逻辑测试,推荐这种Mock方式而非集成测试。只有当需要测试数据库真实交互、事务传播等场景时,才使用
@DataJpaTest或@SpringBootTest启动容器。
浙公网安备 33010602011771号