Spring Boot JUnit单元测试Mock函数返回NPE问题求助
解决JUnit + Spring Boot单元测试中的NullPointerException问题
我帮你分析下这个NullPointerException的核心原因,主要是测试类里注解混用导致依赖注入失败:
问题根源
你同时使用了@WebMvcTest和@RunWith(MockitoJUnitRunner.class)、@InjectMocks,这几个注解的作用存在冲突:
@WebMvcTest是Spring Boot专门用于Web层测试的注解,它会自动创建Spring上下文,并把@MockBean标记的服务注入到Spring管理的控制器实例中。@InjectMocks是Mockito的注解,用于手动创建并注入依赖到目标对象,这个对象和Spring上下文管理的控制器实例不是同一个,导致你直接调用exerciceController.createExcerciceFromDto(dto)时,控制器里的topicServices还是null,从而触发空指针异常。
另外,你用的是JUnit 5(@BeforeEach是JUnit 5的注解),但@RunWith(MockitoJUnitRunner.class)是JUnit 4的运行器,版本不兼容也会加剧注解失效的问题。
解决方案
根据你的测试需求,分两种场景处理:
场景1:测试控制器的POST接口(推荐用MockMvc)
如果是要验证/exercices接口的请求处理逻辑,保留@WebMvcTest,去掉冲突的注解,用MockMvc模拟真实请求:
package com.example.test.controller; import static org.mockito.Mockito.when; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.Arrays; import java.util.HashSet; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import com.example.test.Services.ExerciceServices; import com.example.test.Services.TopicService; import com.example.test.model.Difficulty; import com.example.test.model.Exercice; import com.example.test.model.Status; import com.example.test.model.Topic; import com.example.test.model.dto.ExerciceDTO; import com.fasterxml.jackson.databind.ObjectMapper; @WebMvcTest(ExerciceController.class) class ExerciceControllerTest { @MockBean private ExerciceServices exerciceServices; @MockBean private TopicService topicServices; @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; ExerciceDTO dto = new ExerciceDTO(); Exercice e = new Exercice(); Topic t = new Topic(); @BeforeEach public void setUp() throws Exception { t.setId(6L); t.setTitle("romayssae"); when(topicServices.getTopicById(6L)).thenReturn(Optional.of(t)); e.setDifficulty(Difficulty.EASY); e.setImg("link"); e.setStatus(Status.FINISHED); e.setTitle("title"); e.setTopics(new HashSet<>(Arrays.asList(t))); when(exerciceServices.CreateeExercice(e)).thenReturn(e); } @Test public void givenDTOCreateTheExercice() throws Exception { dto.setDifficulty(Difficulty.EASY); dto.setImg("link"); dto.setStatus(Status.FINISHED); dto.setTitle("title"); dto.setListToppicId(new HashSet<>(Arrays.asList(6L))); mockMvc.perform(post("/exercices") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(dto))) .andExpect(status().isOk()); } }
场景2:单独测试createExcerciceFromDto转换方法(纯单元测试)
如果只是要验证DTO转实体的逻辑,不需要启动Spring上下文,用Mockito的JUnit 5扩展即可:
package com.example.test.controller; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; import java.util.Arrays; import java.util.HashSet; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; 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 com.example.test.Services.ExerciceServices; import com.example.test.Services.TopicService; import com.example.test.model.Difficulty; import com.example.test.model.Exercice; import com.example.test.model.Status; import com.example.test.model.Topic; import com.example.test.model.dto.ExerciceDTO; @ExtendWith(MockitoExtension.class) // JUnit 5官方推荐的Mockito扩展 class ExerciceControllerTest { @Mock private ExerciceServices exerciceServices; @Mock private TopicService topicServices; @InjectMocks private ExerciceController exerciceController; ExerciceDTO dto = new ExerciceDTO(); Exercice e = new Exercice(); Topic t = new Topic(); @BeforeEach public void setUp() throws Exception { // 初始化通用测试数据 } @Test public void givenDTOCreateTheExercice() { dto.setDifficulty(Difficulty.EASY); dto.setImg("link"); dto.setStatus(Status.FINISHED); dto.setTitle("title"); dto.setListToppicId(new HashSet<>(Arrays.asList(6L))); e.setDifficulty(Difficulty.EASY); e.setImg("link"); e.setStatus(Status.FINISHED); e.setTitle("title"); t.setId(6L); t.setTitle("romayssae"); when(topicServices.getTopicById(6L)).thenReturn(Optional.of(t)); Exercice result = exerciceController.createExcerciceFromDto(dto); // 注意:如果要直接assertEquals实体对象,需要Exercice类正确重写equals和hashCode方法 assertEquals(e.getDifficulty(), result.getDifficulty()); assertEquals(e.getImg(), result.getImg()); assertEquals(e.getStatus(), result.getStatus()); assertEquals(e.getTitle(), result.getTitle()); } }
额外注意事项
- 若要直接比较
Exercice对象,必须确保该类正确重写equals和hashCode方法,否则assertEquals会因为对象引用不同而失败。 - JUnit 5优先使用
@ExtendWith(MockitoExtension.class),代替JUnit 4的@RunWith(MockitoJUnitRunner.class)。 @WebMvcTest适合测试Web层的请求处理、参数校验等场景;纯单元测试无需启动Spring上下文,运行速度更快。
内容的提问来源于stack exchange,提问作者louk1397
相关产品推荐
相关产品推荐

