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

使用jBehave结合Mockito测试时出现MockMvc空指针异常

问题分析与解决方案

问题根源

你遇到的MockMvc注入为null的核心原因:jBehave默认通过InstanceStepsFactory手动实例化Steps类,这个实例不在Spring容器的管理范围内。因此@WebMvcTest、@Autowired这类Spring测试注解完全不生效,导致依赖无法注入。

解决方案步骤

1. 修改测试启动类,整合Spring容器

让EmployeeControllerTest继承JUnitStory的同时,加入Spring Boot测试注解,并使用SpringInstanceStepsFactory从Spring容器获取Steps实例,确保Steps类被Spring接管。

修改后的EmployeeControllerTest.java:

package com.donemm.simpc;

import org.jbehave.core.configuration.MostUsefulConfiguration;
import org.jbehave.core.configuration.Configuration;
import org.jbehave.core.io.LoadFromClasspath;
import org.jbehave.core.junit.JUnitStory;
import org.jbehave.core.reporters.StoryReporterBuilder;
import org.jbehave.core.reporters.Format;
import org.jbehave.core.steps.InjectableStepsFactory;
import org.jbehave.core.spring.SpringInstanceStepsFactory;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@WebMvcTest(controllers = EmployeeController.class)
public class EmployeeControllerTest extends JUnitStory {

    private final ApplicationContext applicationContext;

    public EmployeeControllerTest() {
        this.applicationContext = new AnnotationConfigApplicationContext(this.getClass());
    }

    @Override
    public Configuration configuration() {
        return new MostUsefulConfiguration()
                .useStoryLoader(new LoadFromClasspath(this.getClass()))
                .useStoryReporterBuilder(new StoryReporterBuilder()
                        .withDefaultFormats()
                        .withFormats(Format.CONSOLE, Format.TXT, Format.HTML));
    }

    @Override
    public InjectableStepsFactory stepsFactory() {
        // 用Spring实例工厂代替手动new Steps
        return new SpringInstanceStepsFactory(configuration(), applicationContext);
    }
}

2. 调整Steps类,使用Spring Mock注解

替换原有的Mockito手动初始化逻辑,改用Spring提供的@MockBean自动创建并注入Mock实例,同时修正测试中的细节问题。

修改后的EmployeeControllerSteps.java:

package com.donemm.simpc;

import com.donemm.simpc.entity.Employee;
import com.donemm.simpc.repository.EmployeeRepository;
import com.donemm.simpc.services.EmployeeService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.donemm.simpc.controller.EmployeeController;
import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Named;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;
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 org.springframework.test.web.servlet.ResultActions;

import static org.hamcrest.CoreMatchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(controllers = EmployeeController.class)
public class EmployeeControllerSteps {
    
    @Autowired
    private MockMvc mockMvc;
    
    // 用@MockBean让Spring自动创建并注入Mock实例
    @MockBean
    private EmployeeRepository employeeRepository;
    
    @Autowired
    private EmployeeService employeeService;
 
    private Employee employee;
    
    @Given(value="a new Employee record", priority=11)
    public void createEmployee() {
        this.employee = new Employee();
    }
    
    @When(value="the Employee name is <ename>", priority=10)
    public void setEname(@Named("ename") String ename) {
        this.employee.setEname(ename);
    }
    
    @When(value="the Employee id is <id>", priority=9)
    public void setId(@Named("id") int id) {
       this.employee.setId(id); 
    }
    
    @When(value="the Employee dept is <dept>", priority=6)
    public void setDept(@Named("dept") String dname) {
        this.employee.setDept(dname);
    }
    
    @When(value="the method is POST", priority=5)
    public void methodIsGet() {
        // 可根据需求补充逻辑
    }

    @Then(value="the returned values should match", priority=4)
    public void checkValues() throws Exception {
        when(employeeRepository.save(any(Employee.class))).thenReturn(this.employee);
        
        ObjectMapper objectMapper = new ObjectMapper();
            
        // 使用相对路径,WebMvcTest会自动处理上下文
        ResultActions response = mockMvc.perform(post("/employees")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(employee)));
        
        response.andDo(print())
                .andExpect(status().isCreated())
                .andExpect(jsonPath("$.ename", is(employee.getEname())))
                // 修正原代码中字段名错误:实体类是dept,不是dname
                .andExpect(jsonPath("$.dept", is(employee.getDept())));
        
    }
}

关键修正点

  • 替换InstanceStepsFactory为SpringInstanceStepsFactory,让Steps类由Spring容器管理
  • 用@MockBean替代@Mock+@InjectMocks,避免手动初始化Mockito
  • 去掉@AutoConfigureMockMvc,@WebMvcTest已自动配置MockMvc
  • 简化请求URL为相对路径,无需写完整域名端口
  • 修正JSON字段匹配错误(原代码中$.dname与实体类dept字段不匹配)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 23:27:36