如何在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
相关产品推荐
相关产品推荐

