SpringBoot含if语句的待测试类服务测试失败问题排查
问题
我正在为SpringBoot应用编写测试,待测试类UserServiceImpl的getUserById方法返回Optional<User>,该方法通过if语句判断仓库是否返回数据,若未查询到则抛出ResourceNotFoundException。保留if语句时测试始终触发该异常,移除if语句后测试通过,请问我遗漏了什么?
待测试类UserServiceImpl
@Service @RequiredArgsConstructor @Transactional @Slf4j public class UserServiceImpl implements UserService, UserDetailsService { @Autowired private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; @Override public List<User> getUsers() { log.info("Fetching users"); return userRepository.findAll(); } @Override public Optional<User> getUserById(Long id) { log.info("Fetching user id: {}", id); Optional<User> user = userRepository.findById(id); if (!user.isPresent()) { throw new ResourceNotFoundException(MessageUtil.ERROR_USER_NOTFOUND); } return user; } }
测试类UserServiceImplTest
@RunWith(SpringRunner.class) @SpringBootTest class UserServiceImplTest { @MockBean private UserRepository userRepositoryTest; @InjectMocks private UserServiceImpl userServiceTest; @Mock private PasswordEncoder passwordEncoder; private List<User> userSet; private User user1; private User user2; @BeforeEach void setUp() { userServiceTest = new UserServiceImpl(userRepositoryTest, passwordEncoder); Set<ApplicationUserRole> roles = new HashSet<>(); roles.add(ApplicationUserRole.TEST_USER); userSet = new ArrayList<>(); user1 = User.builder().nickname("test-nickname") .id(1L) .username("254701234567") .roles(roles) .password("password") .build(); user2 = User.builder().nickname("test2-nickname2") .id(2L) .username("254701234589") .roles(roles) .password("password") .build(); userSet.add(user1); userSet.add(user2); userSet.stream().forEach(user -> { userServiceTest.saveUser(user); }); } @AfterEach void tearDown() { } @Test void testGetUsers() { when(userServiceTest.getUsers()).thenReturn(userSet); assertEquals(2, userServiceTest.getUsers().size()); verify(userRepositoryTest).findAll(); } @Test void testGetUserById() { when(userServiceTest.getUserById(user1.getId())).thenReturn(Optional.ofNullable(user1)); assertEquals(1, user1.getId()); verify(userRepositoryTest).findById(user1.getId()); } @Test void testSaveUser() { when(userServiceTest.saveUser(user1)).thenReturn(user1); assertEquals(1L, user1.getId()); verify(userRepositoryTest).save(user1); } @Test void updateUser() { user1.setNickname("nickname-update"); when(userServiceTest.saveUser(user1)).thenReturn(user1); assertEquals("nickname-update", user1.getNickname()); verify(userRepositoryTest).save(user1); } }
说明:其他测试均正常运行
问题原因及解决方案
核心问题
你搞反了mock的对象——现在是直接mock服务类的方法,而非mock底层的Repository方法。
比如testGetUserById里的这段代码:
when(userServiceTest.getUserById(user1.getId())).thenReturn(Optional.ofNullable(user1));
这相当于直接给服务方法硬塞返回值,但实际运行时,服务方法内部还是会调用userRepository.findById(id),而你没给这个Repository方法打桩,它默认返回Optional.empty(),自然触发了if语句里的异常。
移除if语句后,服务方法直接返回Repository的结果,但你又mock了服务方法的返回值,所以测试能通过,但这等于跳过了服务类里的逻辑校验,完全没测到你要验证的异常抛出逻辑。
修正方案
1. 修正testGetUserById测试
改成mock Repository的findById方法,再调用服务方法验证逻辑:
@Test void testGetUserById() { // 给Repository的findById打桩,返回预期用户 when(userRepositoryTest.findById(user1.getId())).thenReturn(Optional.of(user1)); // 调用服务方法获取结果 Optional<User> result = userServiceTest.getUserById(user1.getId()); // 验证结果正确性 assertEquals(user1.getId(), result.get().getId()); // 验证Repository方法确实被调用 verify(userRepositoryTest).findById(user1.getId()); }
2. 补充异常场景测试
还应该加一个测试,验证查询不存在的ID时确实会抛出异常:
@Test void testGetUserById_NotFound() { Long nonExistentId = 999L; // 打桩:查询不存在的ID返回空 when(userRepositoryTest.findById(nonExistentId)).thenReturn(Optional.empty()); // 验证抛出指定异常 assertThrows(ResourceNotFoundException.class, () -> { userServiceTest.getUserById(nonExistentId); }); verify(userRepositoryTest).findById(nonExistentId); }
3. 修正其他测试方法
你的其他测试(比如testGetUsers、testSaveUser)也存在同样的问题,都是在mock服务方法而非Repository方法,需要一并修正。比如testGetUsers应该改成:
@Test void testGetUsers() { // mock Repository的findAll方法 when(userRepositoryTest.findAll()).thenReturn(userSet); // 调用服务方法 List<User> result = userServiceTest.getUsers(); // 验证结果 assertEquals(2, result.size()); verify(userRepositoryTest).findAll(); }
内容的提问来源于stack exchange,提问作者Steve Otieno
相关产品推荐
相关产品推荐

