Spring Boot JUnit测试:Service层与Mapper类测试触发空指针异常
问题分析与解决方案
问题描述
运行测试代码时,UserServiceImpl中的userMapper为空对象引用,调用其addRequestUserToEntity方法触发空指针异常。
测试类代码
@RunWith(SpringRunner.class) class UserServiceTest { @InjectMocks UserMapper userMapper = mock(UserMapper.class); @InjectMocks UserServiceImpl userServiceImpl=mock(UserServiceImpl.class); @Mock UserRepository userRepository = mock(UserRepository.class); @Mock UserService userService=mock(UserService.class); @BeforeEach public void setup(){ MockitoAnnotations.initMocks(this); userRepository = Mockito.mock(UserRepository.class); userServiceImpl = new UserServiceImpl(userRepository); } @Test void add() { AddUserRequest addUserRequest = new AddUserRequest("guest","1234567890","guest98787@guest.com","1234567890"); User user = new User(); user.setUserId(1); user.setUid("12345678"); user.setName("guest"); user.setMobileNo("1234567890"); user.setEmail("guest98787@guest.com"); user.setPassword("1234567890"); WalletDetail walletDetail=new WalletDetail(); walletDetail.setWalletId(1); walletDetail.setUid("12345"); walletDetail.setUser(user); walletDetail.setWalletAmount(10.0); user.setWallet(walletDetail); when(userMapper.addRequestUserToEntity(addUserRequest)).thenReturn(user); when(userRepository.save(user)).thenReturn(user); when(userServiceImpl.add(addUserRequest)).thenReturn(user); User result = userServiceImpl.add(addUserRequest); verify(userServiceImpl,times(1)).add(addUserRequest); assertEquals(user.getUserId(), result.getUserId()); } }
Service层代码
@Slf4j @Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Autowired private UserMapper userMapper; @Autowired private WalletService walletService; private UserServiceImpl userServiceImpl; public UserServiceImpl(UserRepository userRepository) { this.userRepository=userRepository; } private UserService userService; public void setUserService(UserService userService) { this.userService = userService; } @Override public User add(AddUserRequest addUserRequest) { log.debug("Request received for user registration: " + addUserRequest); User user = createUser(addUserRequest); WalletDetail walletDetail = walletService.createDefaultWallet(); walletDetail.setUser(user); user.setWallet(walletDetail); log.debug("User saved with data " + user); return userRepository.save(user); } private User createUser(AddUserRequest addUserRequest) { User user = userMapper.addRequestUserToEntity(addUserRequest); user.setCreatedBy(user.getUid()); user.setUpdatedBy(user.getUid()); return user; } }
错误信息
java.lang.NullPointerException: Cannot invoke "bytro.user.management.transformer.UserMapper.addRequestUserToEntity(bytro.user.management.model.request.AddUserRequest)" because "this.userMapper" is null at bytro.user.management.service.impl.UserServiceImpl.createUser(UserServiceImpl.java:64) at bytro.user.management.service.impl.UserServiceImpl.add(UserServiceImpl.java:53) at bytro.user.management.service.UserServiceTest.add(UserServiceTest.java:124) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
问题原因
- 测试类中
userMapper错误使用@InjectMocks注解,该注解用于将Mock对象注入被测试对象,而非标记Mock实例本身。 setup方法中手动创建userServiceImpl时,仅传入了userRepository,未注入userMapper和walletService,导致这两个依赖为空。- 初始时将
userServiceImpl设为Mock对象,会跳过真实业务逻辑的执行,失去测试意义。
解决方案
修改测试类,正确初始化依赖并注入:
@RunWith(SpringRunner.class) class UserServiceTest { // 用@Mock标记需要模拟的依赖 @Mock private UserMapper userMapper; @Mock private UserRepository userRepository; @Mock private WalletService walletService; // 用@InjectMocks标记被测试的真实对象,自动注入Mock依赖 @InjectMocks private UserServiceImpl userServiceImpl; @BeforeEach public void setup(){ MockitoAnnotations.initMocks(this); // 无需手动创建userServiceImpl,@InjectMocks会自动实例化并注入依赖 } @Test void add() { AddUserRequest addUserRequest = new AddUserRequest("guest","1234567890","guest98787@guest.com","1234567890"); User user = new User(); user.setUserId(1); user.setUid("12345678"); user.setName("guest"); user.setMobileNo("1234567890"); user.setEmail("guest98787@guest.com"); user.setPassword("1234567890"); WalletDetail walletDetail=new WalletDetail(); walletDetail.setWalletId(1); walletDetail.setUid("12345"); walletDetail.setUser(user); walletDetail.setWalletAmount(10.0); user.setWallet(walletDetail); // 模拟walletService的方法返回值 when(walletService.createDefaultWallet()).thenReturn(walletDetail); when(userMapper.addRequestUserToEntity(addUserRequest)).thenReturn(user); when(userRepository.save(user)).thenReturn(user); // 调用真实的Service方法 User result = userServiceImpl.add(addUserRequest); // 验证依赖的调用次数 verify(userRepository,times(1)).save(user); verify(userMapper,times(1)).addRequestUserToEntity(addUserRequest); verify(walletService,times(1)).createDefaultWallet(); assertEquals(user.getUserId(), result.getUserId()); } }
关键修改点
- 将
userMapper、walletService改为@Mock标记,明确这些是需要模拟的依赖。 - 被测试对象
userServiceImpl用@InjectMocks标记,Mockito自动完成实例化和依赖注入。 - 移除手动创建
userServiceImpl的代码,避免覆盖注入后的实例。 - 补充模拟
walletService.createDefaultWallet()的返回值,避免新增空指针。 - 调整验证逻辑,改为验证真实依赖的调用次数,确保业务流程符合预期。
内容的提问来源于stack exchange,提问作者Anup Panchal
相关产品推荐
相关产品推荐

