You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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());
    }
}

额外注意事项

  1. 若要直接比较Exercice对象,必须确保该类正确重写equals和hashCode方法,否则assertEquals会因为对象引用不同而失败。
  2. JUnit 5优先使用@ExtendWith(MockitoExtension.class),代替JUnit 4的@RunWith(MockitoJUnitRunner.class)。
  3. @WebMvcTest适合测试Web层的请求处理、参数校验等场景;纯单元测试无需启动Spring上下文,运行速度更快。

内容的提问来源于stack exchange,提问作者louk1397

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 16:22:48