Spring Boot Test JUnit空指针异常求助:单元测试新手的服务测试问题
解决Spring Service单元测试的JUnit空指针异常
兄弟,我刚入门写单元测试的时候也踩过一模一样的坑!你的空指针大概率是因为Service里的TopicRepository没被正确初始化——毕竟Spring的@Autowired是靠容器注入的,但普通JUnit测试默认不会启动Spring容器,或者你没做好依赖Mock。结合你的TopicService代码,给你两种最常用的解决思路:
方法一:用Mockito做纯单元测试(推荐)
这种方式不需要启动Spring容器,只Mock掉TopicRepository,专注测试Service的业务逻辑,速度快还能隔离依赖。
步骤&示例代码:
- 给测试类加
@ExtendWith(MockitoExtension.class)(JUnit 5写法,JUnit 4用@RunWith(MockitoJUnitRunner.class)) - 用
@InjectMocks标注你的TopicService实例,Mockito会自动把Mock的依赖注入进去 - 用
@Mock标注TopicRepository,创建一个Mock对象代替真实的Repository
package com.praxis.topics.service; import com.praxis.topics.exception.EntityNotFoundException; import com.praxis.topics.model.Topic; import com.praxis.topics.model.enums.Status; import com.praxis.topics.repository.TopicRepository; 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.time.LocalDateTime; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class TopicServiceTest { @Mock private TopicRepository topicRepository; // Mock掉Repository,避免依赖真实数据库 @InjectMocks private TopicService topicService; // 自动把Mock的Repository注入到Service里 @Test void whenGetExistingTopic_thenReturnTopic() { // 准备测试用的Mock数据 Topic mockTopic = new Topic(); mockTopic.setId(1L); mockTopic.setTitle("JUnit测试入门"); mockTopic.setStatus(Status.ACTIVE); mockTopic.setCreatedAt(LocalDateTime.now()); // 定义Mock行为:当调用findById(1L)时返回我们准备好的mockTopic when(topicRepository.findById(1L)).thenReturn(Optional.of(mockTopic)); // 调用Service的方法 Topic result = topicService.getTopicById(1L); // 验证结果是否符合预期 assertNotNull(result); assertEquals("JUnit测试入门", result.getTitle()); // 还能验证Repository的方法是否被正确调用过 verify(topicRepository, times(1)).findById(1L); } @Test void whenGetNonExistingTopic_thenThrowEntityNotFoundException() { // 定义Mock行为:找不到对应ID时返回空Optional when(topicRepository.findById(999L)).thenReturn(Optional.empty()); // 断言会抛出预期的异常 assertThrows(EntityNotFoundException.class, () -> topicService.getTopicById(999L)); } }
为什么能解决NPE?
@Mock会创建一个TopicRepository的Mock实例,@InjectMocks会自动把这个Mock注入到TopicService的对应字段里,这样Service里的topicRepository就不再是null,调用方法自然不会空指针了。
方法二:用Spring Boot Test做集成测试
如果你想测试Service和真实Repository的交互(比如和数据库的CRUD操作),可以用Spring Boot Test启动完整的Spring容器:
步骤&示例代码:
- 测试类加
@SpringBootTest,启动Spring容器 - 用
@Autowired注入TopicService和TopicRepository(如果不想用真实Repository,也可以用@MockBean替换)
package com.praxis.topics.service; import com.praxis.topics.model.Topic; import com.praxis.topics.model.enums.Status; import com.praxis.topics.repository.TopicRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.time.LocalDateTime; import static org.junit.jupiter.api.Assertions.*; @SpringBootTest class TopicServiceIntegrationTest { @Autowired private TopicService topicService; @Autowired private TopicRepository topicRepository; @Test void whenCreateTopic_thenTopicIsSaved() { // 准备测试数据 Topic newTopic = new Topic(); newTopic.setTitle("集成测试示例"); newTopic.setStatus(Status.DRAFT); newTopic.setCreatedAt(LocalDateTime.now()); // 调用Service的创建方法 Topic savedTopic = topicService.createTopic(newTopic); // 验证保存结果 assertNotNull(savedTopic.getId()); assertEquals("集成测试示例", savedTopic.getTitle()); // 验证数据库中确实存在这条数据 assertTrue(topicRepository.existsById(savedTopic.getId())); } }
注意点:
这种方式启动Spring容器的速度较慢,适合做集成测试;纯单元测试还是推荐第一种Mockito的方式,保证测试的独立性和执行效率。
额外提醒的坑:
- 不要忘记加Mockito的扩展类/运行器,否则
@Mock和@InjectMocks不会生效,还是会NPE - 单元测试尽量Mock外部依赖,不要直接连真实数据库,避免测试受环境影响
内容的提问来源于stack exchange,提问作者Jefer
相关产品推荐
相关产品推荐

