spring-test提供的MockMvc应该怎么用

732 阅读1分钟

问题

下述利用MockMvc做单测的代码,运行时usermapper会出现NPE

public class HelloController {
    @Autowired
    private UserMapper userMapper;
    
    @RequestMapping("/user")
    public User getUser() {
        return userMapper.selectOne(1);
    }
}

@SpringBootTest
public class HelloControllerTest {
    private MockMvc mockMvc;
    @BeforeEach
    public void setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(new HelloController()).build();
        // 改成下述代码不会出现NPE
        // mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
    }
    
    @Test
    public void testGetUser() {
        mockMvc.perform(MockMvcRequestBuilders.get("/user"))
        .andDo(MockMvcResultHandlers.print())
        .andReturn();
    }
}

解析

留意MockMvcRequestBuilders有两个构造方法,standaloneSetup是用来注册@Controller和Spring MVC底层组件的,不会注册其他用户自定义的Component,所以usermapper会出现NPE.
webAppContextSetup会初始化整个context,也当然也包括用户注册的Component,因此不会出现NPE

其他写法

总觉得上述写法不太优雅,如setup()被执行多次等,Spring提供了另外一种写法

@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testGetUser() {
        mockMvc.perform(MockMvcRequestBuilders.get("/user"))
        .andDo(MockMvcResultHandlers.print())
        .andReturn();
    }
}

注意这里多了@AutoConfigureMockMvc,他会导入MockMvcAutoConfiguration,自动装配出MockMvcBuilder,进而得到MockMvc

public class MockMvcAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean(MockMvcBuilder.class)
    public DefaultMockMvcBuilder mockMvcBuilder(List<MockMvcBuilderCustomizer> customizers) {
       // 全初始化context
       DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(this.context);
       builder.addDispatcherServletCustomizer(new MockMvcDispatcherServletCustomizer(this.webMvcProperties));
       for (MockMvcBuilderCustomizer customizer : customizers) {
          customizer.customize(builder);
       }
       return builder;
    }
    @Bean
    @ConditionalOnMissingBean
    public MockMvc mockMvc(MockMvcBuilder builder) {
       // 构造出MockMvc,后面使用注入即可
       return builder.build();
    }
    // 其余省略...
}

@WebMvcTest和@SpringBootTest有什么不同呢

@SpringBootTest里面包含了

  • @BootstrapWith(SpringBootTestContextBootstrapper.class),用于查找启动装配类(@SpringBootConfiguration)
  • @ExtendWith(SpringExtension.class),是spring-test和junit5的集成 @WebMvcTest同样包含上述两个功能,除此之外还有:
  • @TypeExcludeFilters(WebMvcTypeExcludeFilter.class), 只会注册@Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver, 不包括 @Component, @Service @Repository
  • @AutoConfigureCache, 启用缓存
  • @AutoConfigureWebMvc, 启用WebMvc
  • @AutoConfigureMockMvc, 启用MockMvc

可以看出@WebMvcTest基本包含了@SpringBootTest的功能,但前者去掉了@Component, @Service, @Repository,可以认为只针对Controller测试,更加轻量,这是两者的不同之处.