使用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
相关产品推荐
相关产品推荐

