【测试相关】通过@WebMvcTest测试Spring Boot
本文使用的是Spring Boot 5.7.0 version。
- 关于Spring Boot与JUnit的关系,参考:【测试相关】Spring Boot测试介绍,与JUnit4, Junit5的集成
- 关于JUnit5,参考:【测试相关】JUnit5介绍
- 关于Mockito,参考:【测试相关】Java Mock框架之Mockito
1. @WebMvcTest
注解
官网关于Spring MVC相关的文档:https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing.spring-boot-applications.spring-mvc-tests
用位于spring-boot-test-autoconfigure
包中的@WebMvcTest
注解来测试Spring MVC Controller的行为(主要是会为我们自动注入一个MockMvc
对象,Spring MVC Server端测试的主要类,MockMvc
位于spring-test
包中)。
@WebMvcTest
注解通常需要传入目标Controller类,如@WebMvcTest(BookController.class)
,即它会自动扫描BookController类以及注入Spring MVC相关的配置,扫描的配置列举如下:
@Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, HandlerInterceptor, WebMvcConfigurer, WebMvcRegistrations, and HandlerMethodArgumentResolver
如果BookController中有BookService,@WebMvcTest
并不会自动帮我们装配,需要我们通过@MockBean来声明一个Mock对象,并且使用Mockito的when/then/...来Mock BookService的行为(后面有示例)。
【一些其它的点】
- 值得注解的是:
@Component
注解的配置以及@ConfigurationProperties
并不会被扫描。 - 可以使用
@EnableConfigurationProperties
来开始对@ConfigurationProperties
的扫描。 -
@WebMvcTest
默认开启的auto-configuration,可以在 页面 中找到。 - 另外,如果想要再额外include某些配置,可以使用
@Import
。 - 想要达到
@SpringBootTest
这样的全局扫描,也可以使用@AutoConfigureMockMvc
来代替@WebMvcTest
,并且需要加上@SpringBootTest
注解。
1.1 @WebMvcTest
vs @SpringBootTest
在项目中新建一个Configuration:
@Configuration
public class BookConfiguration {
@Bean
public BookDomain bookDomain() {
return new BookDomain(1001, "test bean");
}
}
@SpringBootTest
会自动装配@SpringBootApplication类所有的包以及子包下所有的Bean:
@SpringBootTest
public class BookConfigurationTest {
@Autowired
private BookDomain bookDomain;
@Test
public void beanTest() {
Assertions.assertEquals(1001, bookDomain.getId());
Assertions.assertEquals("test bean", bookDomain.getName());
}
}
我们使用@WebMvcTest
测试目标Controller:BookController,目的是为了测试会不会自动扫描@Configuration配置:
@WebMvcTest(BookController.class)
public class BookControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
BookService bookService;
@Autowired
private BookDomain bookDomain;
@Test
public void test() {
}
会报错:> nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.domain.BookDomain' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
即@WebMvcTest
并不会include @Configuration
的配置。
-
@WebMvcTest
如同上述介绍的,会扫描它定义的Controller,并且引入Spring MVC相关的配置,也会自动注入MockMvc
instance,但并不会全局扫描并include所有的bean(比如不会扫描@Configuration
配置)。 -
SpringBootTest
则会启动整个Spring Application Context
,所以@Configuration
的配置自然也会被扫描以及注解,但并不会注入Spring MVC相关的比如MockMvc
。
1.2 若想include @Configuration,可以使用AutoConfigureMockMvc
在上述#1.1的BookControllerTest
,我们无法通过@WebMvcTest(BookController.class)
,来include @Configuration的配置,相应的,我们可以改用@AutoConfigureMockMvc
+@SpringBootTest
的方式:
@AutoConfigureMockMvc
@SpringBootTest
public class BookControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
BookService bookService;
@Autowired
private BookDomain bookDomain;
@Test
public void beanTest() {
Assertions.assertEquals(1001, bookDomain.getId());
Assertions.assertEquals("test bean", bookDomain.getName());
}
}
2. 测试get以及post APIs
以下示例测试了BookController中的两个APIs:
- GET -
/books
,返回BookDomain list。 - POST -
/books
,新增Book,接收RequestBody为BookDomain。
一些解释:
- 下述中的get,post方法,是位于
org.springframework.test.web.servlet.request
包下,MockMvcRequestBuilders
类中,都是static方法。 - 关于BookService的行为,使用Mockito进行Mock。
- 除了能测试response status = 200外,也能测其它错误的Response。
- print()则是打印结果。
- 测试MVC的写法很灵活,也可以测各种MediaType,比如
application/json
或text/html
等等。
@WebMvcTest(BookController.class)
public class BookControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
BookService bookService;
@Test
public void listTest() throws Exception {
List<BookDomain> list = new ArrayList<>();
list.add(new BookDomain(1, "test"));
Mockito.when(bookService.list()).thenReturn(list);
MvcResult mvcResult = this.mockMvc.perform(get("/books").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print())
.andReturn();
System.out.println(mvcResult.getResponse().getContentAsString());
Assertions.assertTrue(mvcResult.getResponse().getContentAsString().contains("test"));
}
@Test
public void saveTest() throws Exception {
Mockito.doNothing().when(bookService).save(Mockito.isA(BookDomain.class));
JsonMapper jsonMapper = new JsonMapper();
MvcResult mvcResult = this.mockMvc.perform(post("/books")
.contentType(MediaType.APPLICATION_JSON).content(jsonMapper.writeValueAsBytes(new BookDomain(1, "asdf"))))
.andExpect(status().isOk())
.andReturn();
Assertions.assertEquals("true", mvcResult.getResponse().getContentAsString());
}
3. 通过MockMvc
来测试我们自定义的Filter
假设我们有UserFilter,然后我们想要通过MockMvc
来测试自定义的Filter。
首先,自定义Filter有两种实现方式:
- 第一种方式:在Spring Boot启动类中加
@ServletComponentScan
,并且在我们的UserFilter上加注解:@WebFilter(filterName = "userFilter", urlPatterns = "/*")
- 第二种方式,以Bean的方式注入。通过
FilterRegistrationBean
新建Filter并返回,该方法标注为@Bean
。
根据#1介绍的,@WebMvcTest
注解只会扫描它定义的Controller类,并会忽略一切@Configuration
,所以我们要使用#1.2的方式,即:@AutoConfigureMockMvc
+@SpringBootTest
以下是代码示例。
首先新建UserFilter类:
public class UserFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("user filter");
filterChain.doFilter(servletRequest, servletResponse);
}
}
定义Configuration配置类:
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean userFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new UserFilter());
registration.addUrlPatterns("/*");
return registration;
}
}
沿用上述的BookControllerTest类:
@SpringBootTest
@AutoConfigureMockMvc
public class BookControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
BookService bookService;
@Test
public void listTest() throws Exception {
// 略
}
}
这样,在测试listTest的时候,会测试API为/books
(GET方法),最后会进入我们自定义的UserFilter。

【参考】