Spring MVC测试能否不使用@MockBean?有无更优实现方案?
解决@WebMvcTest中冗余@MockBean的问题
你遇到的这个场景太常见了——控制器依赖了多个服务,但当前测试只用到其中一个,却被迫给所有依赖都加上@MockBean,不仅代码显得冗余,还会让读代码的人误以为这些未使用的服务和测试有关。下面分两种情况给你提供解决方案:
一、完全不用@MockBean:手动构建MockMvc
如果不需要Spring测试上下文带来的自动配置(比如自定义的消息转换器、过滤器等),可以完全脱离上下文,手动实例化控制器并构建MockMvc。这种方式更轻量,也彻底避免了处理未使用的依赖。
示例代码:
@RunWith(SpringRunner.class) class MyControllerTest { private MockMvc mvc; private MyFirstService s1 = Mockito.mock(MyFirstService.class); @Before public void setup() { // 手动创建控制器实例,只传入测试需要的s1 MyController controller = new MyController(s1); // 用standaloneSetup构建MockMvc mvc = MockMvcBuilders.standaloneSetup(controller).build(); } @Test public void testMethod() throws Exception { // 模拟s1的行为 when(s1.someMethod(any())).thenReturn(...); // 执行请求并验证结果 MvcResult mvcResult = mvc.perform(post("/products") .header("Content-Type", "application/json") .content(json)) .andExpect(status().isCreated()) .andReturn(); } }
这种方式的优势:
- 完全不用管
MySecondService和MyThirdService,代码更简洁 - 测试启动速度更快,因为不需要加载Spring上下文
- 依赖关系一目了然,读者能立刻知道测试用到了哪些组件
二、保留@MockBean的优化方案
如果你需要保留Spring测试上下文的特性(比如自动配置的HTTP消息转换器、拦截器等),可以通过以下方式优化@MockBean的使用:
1. 用@MockBeans批量声明
把多个@MockBean合并到一个@MockBeans注解中,减少重复代码,让结构更紧凑:
@RunWith(SpringRunner.class) @WebMvcTest(MyController.class) class MyControllerTest { @MockBeans({ @MockBean(MyFirstService.class), @MockBean(MySecondService.class), @MockBean(MyThirdService.class) }) private MyFirstService s1; // 只注入需要用到的服务实例 @Autowired private MockMvc mvc; @Test public void testMethod() throws Exception { // 模拟s1的行为 when(s1.someMethod(any())).thenReturn(...); // 执行请求验证 MvcResult mvcResult = mvc.perform(post("/products") .header("Content-Type", "application/json") .content(json)) .andExpect(status().isCreated()) .andReturn(); } }
这种写法虽然还是要声明所有依赖,但避免了一堆分散的@MockBean注解,代码看起来更整洁。
2. 用测试配置类集中管理Mock
创建一个测试专用的配置类,在里面批量创建所有需要的Mock Bean,然后在测试类中引入这个配置:
// 测试配置类,集中创建Mock @TestConfiguration class ControllerTestConfig { @Bean @Primary public MyFirstService myFirstService() { return Mockito.mock(MyFirstService.class); } @Bean @Primary public MySecondService mySecondService() { return Mockito.mock(MySecondService.class); } @Bean @Primary public MyThirdService myThirdService() { return Mockito.mock(MyThirdService.class); } } // 测试类 @RunWith(SpringRunner.class) @WebMvcTest(MyController.class) @Import(ControllerTestConfig.class) class MyControllerTest { @Autowired private MyFirstService s1; @Autowired private MockMvc mvc; @Test public void testMethod() throws Exception { // 模拟s1的行为 when(s1.someMethod(any())).thenReturn(...); // 执行请求验证 MvcResult mvcResult = mvc.perform(post("/products") .header("Content-Type", "application/json") .content(json)) .andExpect(status().isCreated()) .andReturn(); } }
这种方式适合多个测试类需要相同Mock配置的场景,可以复用配置类,减少重复代码。
内容的提问来源于stack exchange,提问作者Dherik
相关产品推荐
相关产品推荐

