如何让MockMvc与构造函数含HttpServletRequest的控制器共用同一请求实例?
问题分析
你的问题核心在于HttpServletRequest的作用域不匹配:
- 正常Spring环境中,HttpServletRequest是请求作用域的Bean,每个请求都会生成新实例,容器会自动把当前请求的实例注入到需要的地方。
- 但你在测试时手动创建了一个
MockHttpServletRequest并通过构造函数注入到控制器,控制器会一直持有这个固定实例;而MockMvc每次发起请求时,会创建全新的请求实例来处理请求,两者完全独立,所以控制器里拿不到MockMvc设置的Accept头。
解决方案
方案1:修改控制器的请求获取方式(推荐)
不要通过构造函数注入HttpServletRequest,改用更符合Spring规范的方式:
方式A:方法参数直接注入
在控制器方法里直接添加HttpServletRequest参数,Spring会自动把当前请求的实例传入:
@Override public ResponseEntity<String> getHappyBirthday( @ApiParam(value = "Filter by name") @Valid @RequestParam(value = "name", required = false) String name, HttpServletRequest request // 直接作为方法参数 ) { String accept = request.getHeader("Accept"); if (accept == null || !accept.equals(MediaType.APPLICATION_JSON_VALUE)) throw new UnsupportedMediaTypeException("Accept header must be " + MediaType.APPLICATION_JSON_VALUE); String message = "Happy Birthday " + name; return new ResponseEntity<>(message, HttpStatus.OK); }
这样测试时,MockMvc会自动把当前请求的实例传入方法,不需要手动注入,自然能拿到Accept头。
方式B:使用RequestContextHolder获取
如果不想修改方法参数,可以用RequestContextHolder获取当前请求:
@Override public ResponseEntity<String> getHappyBirthday( @ApiParam(value = "Filter by name") @Valid @RequestParam(value = "name", required = false) String name ) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String accept = request.getHeader("Accept"); if (accept == null || !accept.equals(MediaType.APPLICATION_JSON_VALUE)) throw new UnsupportedMediaTypeException("Accept header must be " + MediaType.APPLICATION_JSON_VALUE); String message = "Happy Birthday " + name; return new ResponseEntity<>(message, HttpStatus.OK); }
这种方式不需要修改控制器的构造函数,也能拿到当前请求的实例。
方式C:构造注入时添加@Lazy
如果坚持要构造注入,给HttpServletRequest添加@Lazy注解,让Spring延迟到请求时才注入当前请求的实例:
@Autowired public HappyBirthdayController(ObjectMapper objectMapper, @Lazy HttpServletRequest request) { this.objectMapper = objectMapper; this.request = request; }
注意:这种方式依赖Spring的代理机制,测试时需要用MockMvcBuilders.webAppContextSetup而非standaloneSetup,因为standaloneSetup不会完整初始化Spring上下文。
方案2:修改测试代码(仅适配现有控制器结构,不推荐)
如果不能修改控制器代码,可以在测试时,通过MockMvc的requestPostProcessor把请求头同步到控制器持有的MockHttpServletRequest实例:
@Test public void getHappyBirthdayTest() throws Exception{ mockMvc.perform(get("/birthday-cards?name=Jack") .accept(MediaType.APPLICATION_JSON) .requestPostProcessor(request -> { // 把MockMvc当前请求的Accept头同步到控制器持有的实例 mockHttpServletRequest.setHeader("Accept", MediaType.APPLICATION_JSON_VALUE); return request; })) .andExpect(status().isOk()) .andDo(print()); }
这种方式是临时适配,需要手动同步每个请求头,维护成本高,仅作为应急方案。
测试代码额外修正点
你的测试代码存在两处冗余/错误:
- 控制器类名不一致:
HappyBirthdayApiController和HappyBirthdayController需保持统一; @InjectMocks和手动new控制器重复,建议去掉手动new,让Mockito自动注入:
@Before public void setUp(){ MockitoAnnotations.initMocks(this); mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); }
内容的提问来源于stack exchange,提问作者user2821694
相关产品推荐
相关产品推荐

