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

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

这种方式是临时适配,需要手动同步每个请求头,维护成本高,仅作为应急方案。

测试代码额外修正点

你的测试代码存在两处冗余/错误:

  1. 控制器类名不一致:HappyBirthdayApiController 和 HappyBirthdayController 需保持统一;
  2. @InjectMocks 和手动new控制器重复,建议去掉手动new,让Mockito自动注入:
@Before
public void setUp(){
    MockitoAnnotations.initMocks(this);
    mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 04:33:43