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

如何在JUnit测试中让Spring控制器内部类使用@MockBean?

Spring Controller单元测试问题解决

问题场景

需要为以下Spring Controller编写单元测试,目标是用@MockBean模拟控制器依赖的MagicWriter和MagicUpdater,但当前测试代码无法正常生效:

控制器代码

@RestController
@RequestMapping("/")
public class MyCustomController {    
    @Autowired
    private MagicWriter magicWriter;

    @Autowired
    private MagicUpdater magicUpdater;

    @RequestMapping(path = "/", method = RequestMethod.POST)
    public String postMagicMethod(@RequestParam(name = "SomeParam") String param1) {
        var magicHandler = new MagicHandler(magicWriter, magicUpdater);
        return magicHandler.doSomeMagic();
    }
}

原测试代码

@SpringJUnitConfig
@WebMvcTest(value= MyCustomController.class)
public class MyCustomControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private MagicWriter magicWriter;

    @MockBean
    private MagicUpdater magicUpdater;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Configuration
    static class Config {
        @Bean
        MyCustomController dispatchController() {
            return new MyCustomController();
        }
    }

    @Test
    void basicTest() throws Exception {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();

        HttpHeaders headers = new HttpHeaders();
        // Added some http headers

        String uri = "/";

        RequestBuilder request = MockMvcRequestBuilders.request(HttpMethod.POST, uri, headers);
        MvcResult result =  mockMvc.perform(request).andReturn();
        assertThat(result.getResponse().getContentAsString()).isEqualTo(expected);
    }
}

问题分析

原测试代码存在几个关键问题:

  • 手动创建控制器Bean导致依赖注入失效:@WebMvcTest会自动扫描并实例化目标控制器,手动通过静态Config类创建控制器实例时,Spring无法将@MockBean注入到这个手动创建的对象中,导致模拟失效。
  • 重复构建MockMvc:@WebMvcTest已经自动配置并注入了MockMvc实例,无需通过webAppContextSetup重新构建。
  • 缺少必填请求参数:控制器方法要求@RequestParam("SomeParam"),但测试请求中未传递该参数,会触发400错误。
  • 未模拟MockBean的方法行为:没有指定magicWriter和magicUpdater的方法返回值或行为,无法确保MagicHandler.doSomeMagic()返回预期结果。

修正后的测试代码

@WebMvcTest(MyCustomController.class)
public class MyCustomControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private MagicWriter magicWriter;

    @MockBean
    private MagicUpdater magicUpdater;

    @Test
    void basicTest() throws Exception {
        // 预期返回值
        String expected = "expectedMagicResult";

        // 根据MagicHandler.doSomeMagic()的逻辑,模拟依赖方法的行为
        // 示例:如果doSomeMagic调用magicWriter.write(),则模拟该方法
        // doNothing().when(magicWriter).write(anyString());
        // 如果doSomeMagic依赖magicUpdater的返回值,模拟返回对应结果
        // when(magicUpdater.update(anyString())).thenReturn("updatedValue");

        mockMvc.perform(MockMvcRequestBuilders.post("/")
                        // 传递必填的请求参数
                        .param("SomeParam", "testParamValue")
                        // 如需添加请求头,可直接链式调用
                        // .headers(headers)
                )
                .andExpect(status().isOk())
                .andExpect(content().string(expected));
    }
}

优化方案(可选)

如果希望进一步简化测试,可以重构控制器,将MagicHandler改为Spring管理的Bean,这样可以直接模拟MagicHandler而非底层依赖:

重构后的控制器

@RestController
@RequestMapping("/")
public class MyCustomController {    
    private final MagicHandler magicHandler;

    // 优先使用构造注入
    public MyCustomController(MagicWriter magicWriter, MagicUpdater magicUpdater) {
        this.magicHandler = new MagicHandler(magicWriter, magicUpdater);
    }

    @PostMapping("/")
    public String postMagicMethod(@RequestParam(name = "SomeParam") String param1) {
        return magicHandler.doSomeMagic();
    }
}

// 补充配置类,将MagicHandler注册为Bean(或使用@Component注解标记MagicHandler)
@Configuration
public class MagicConfig {
    @Bean
    public MagicHandler magicHandler(MagicWriter magicWriter, MagicUpdater magicUpdater) {
        return new MagicHandler(magicWriter, magicUpdater);
    }
}

简化后的测试代码

@WebMvcTest(MyCustomController.class)
public class MyCustomControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private MagicHandler magicHandler;

    @Test
    void basicTest() throws Exception {
        String expected = "expectedMagicResult";
        // 直接模拟MagicHandler的方法返回值
        when(magicHandler.doSomeMagic()).thenReturn(expected);

        mockMvc.perform(MockMvcRequestBuilders.post("/")
                        .param("SomeParam", "testParamValue"))
                .andExpect(status().isOk())
                .andExpect(content().string(expected));
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 14:30:43