Spring Boot Mockito中使用@InjectMocks仅Mock指定依赖的实现方法
问题背景
你有一个标注了@Service的UserServiceImpl类,依赖UserRepository(希望保留真实实例)和SessionService(需要Mock)。当前测试中,Mockito成功Mock了SessionService,但UserRepository却为null,导致调用save()方法时抛出空指针异常,你想知道是否可以只MockSessionService而保留真实的UserRepository,以及如何实现。
你的服务类代码:
@Service @Slf4j public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Autowired private SessionService sessionService; @Override public User create(User user) { log.info("User Creation at Service"); // ... 执行一些验证操作 .. // 该方法需要在单元测试中Mock String returnValue = sessionService.doSomethingThatIDontWantInTests(); user.setInternalKey(returnValue); // .. 执行更多验证操作 .. return userRepository.save(user); } }
测试类代码:
@SpringBootTest class UserServiceTest { @InjectMocks private UserServiceImpl userService; @Mock private SessionService sessionService; @Test void CreateUserTest() { Mockito.when(sessionService.doSomethingThatIDontWantInTests()).thenReturn("abcxyz123321"); User user = new User(); user.setName("John Doe"); user.setEmail("john.doe@example.com"); User savedUser = userService.create(user); assertNotNull(savedUser.getUserId()); } }
错误信息:
java.lang.NullPointerException: Cannot invoke "com.myproject.data.repos.UserRepository.save(User)" because "this.userRepository" is null
为什么原来的方法会失败?
你用的@InjectMocks是Mockito的注解,它的作用是创建被测试类的实例,并手动注入用@Mock/@Spy标注的依赖,但它完全不会和Spring容器交互。也就是说,UserRepository作为Spring管理的Bean,@InjectMocks根本不知道它的存在,自然不会注入,导致null。
解决方案:只Mock SessionService,保留真实UserRepository
完全可以实现你的需求,只需要改用Spring Boot提供的测试注解,让Spring容器帮你处理依赖注入:
方法1:使用@SpringBootTest + @MockBean + @Autowired
@MockBean是Spring Boot的核心测试注解,它会自动将Spring容器中对应的Bean替换成Mock实例,而其他依赖(比如UserRepository)会正常从容器中获取真实实例。修改后的测试类如下:
@SpringBootTest class UserServiceTest { // 直接从Spring容器注入真实的UserServiceImpl实例 @Autowired private UserServiceImpl userService; // 将Spring容器中的SessionService替换为Mock实例 @MockBean private SessionService sessionService; @Test void CreateUserTest() { // 配置Mock的返回值 Mockito.when(sessionService.doSomethingThatIDontWantInTests()).thenReturn("abcxyz123321"); User user = new User(); user.setName("John Doe"); user.setEmail("john.doe@example.com"); User savedUser = userService.create(user); assertNotNull(savedUser.getUserId()); } }
方法2:使用轻量测试切片(Spring Boot 3.1+)
如果不想加载完整的Spring上下文(启动更快),可以用Spring Boot 3.1及以上版本提供的@ServiceTest切片注解,它只会加载服务层相关的Bean:
@ServiceTest class UserServiceTest { @Autowired private UserServiceImpl userService; @MockBean private SessionService sessionService; @Test void CreateUserTest() { Mockito.when(sessionService.doSomethingThatIDontWantInTests()).thenReturn("abcxyz123321"); User user = new User(); user.setName("John Doe"); user.setEmail("john.doe@example.com"); User savedUser = userService.create(user); assertNotNull(savedUser.getUserId()); } }
总结
你完全不需要MockUserRepository,通过Spring Boot的@MockBean注解,就能轻松实现只替换SessionService为Mock实例,同时保留UserRepository的真实实例。核心是让Spring容器管理依赖注入,而不是只依赖Mockito的@InjectMocks。
内容的提问来源于stack exchange,提问作者Sriram Sridharan

