1. 概述

让我们深入探索Spring Boot测试的世界!本文将深度解析@SpringBootTest@WebMvcTest注解,探讨它们的使用场景、工作原理以及如何协同测试Spring Boot应用。我们还将揭示MockMvc的内部机制,以及它在集成测试中与这两个注解的交互方式。

2. @WebMvcTest和@SpringBootTest简介

@WebMvcTest注解专门用于MVC(更具体说是控制器)层测试,可配置为测试特定控制器。它主要加载Web层组件,简化相关测试流程。

@SpringBootTest注解通过加载完整的应用上下文(如带@Component@Service注解的类、数据库连接等)创建测试环境。它会查找主类(带@SpringBootApplication注解)并以此启动应用上下文。

这两个注解均在Spring Boot 1.4版本引入。

3. 项目搭建

本教程创建两个类:SortingControllerSortingServiceSortingController接收包含整数列表的请求,调用包含业务逻辑的SortingService对列表排序。

我们使用构造函数注入获取SortingService依赖:

@RestController
public class SortingController {
    private final SortingService sortingService;

    public SortingController(SortingService sortingService){
        this.sortingService=sortingService;
    }    // ...
}

声明GET方法检查服务运行状态,同时帮助探索注解在测试中的工作方式:

@GetMapping
public ResponseEntity<String> helloWorld(){
    return ResponseEntity.ok("Hello, World!");
}

添加POST方法接收JSON数组并返回排序结果,测试此类方法有助于理解MockMvc的用法:

@PostMapping
public ResponseEntity<List<Integer>> sort(@RequestBody List<Integer> arr){
    return ResponseEntity.ok(sortingService.sortArray(arr));
}

4. @SpringBootTest和@WebMvcTest对比

@WebMvcTest位于org.springframework.boot.test.autoconfigure.web.servlet包,而@SpringBootTest位于org.springframework.boot.test.context包。Spring Boot默认添加必要测试依赖,类级别每次只能使用其中一个注解。

4.1. 使用MockMvc

@SpringBootTest上下文中,MockMvc会直接调用控制器中的实际服务实现。服务层bean在应用上下文中可用。使用MockMvc需添加@AutoConfigureMockMvc注解,它会自动配置并注入mockMvc实例:

@AutoConfigureMockMvc
@SpringBootTest
class SortingControllerIntegrationTest {
    @Autowired
    private MockMvc mockMvc;
}

@WebMvcTest中,MockMvc配合服务层的@MockBean使用,可模拟服务响应而不调用真实服务。服务层bean不包含在应用上下文中,且默认提供@AutoConfigureMockMvc

@WebMvcTest
class SortingControllerUnitTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private SortingService sortingService;
}

⚠️ 注意:当@SpringBootTest配合webEnvironment=RANDOM_PORT使用时需谨慎,因为MockMvc确保Web请求处理组件就绪但不启动Servlet容器,而webEnvironment=RANDOM_PORT会尝试启动Servlet容器,两者存在冲突。

4.2. 自动配置了什么?

@WebMvcTest自动配置以下组件:

  • MockMvc实例
  • DispatcherServlet
  • HandlerMapping
  • HandlerAdapter
  • ViewResolvers
  • 扫描@Controller@ControllerAdvice@JsonComponentConverterGenericConverterFilterWebMvcConfigurerHandlerMethodArgumentResolver等组件

✅ 主要自动配置Web层相关组件

@SpringBootTest加载@SpringBootApplication(SpringBootConfiguration+EnableAutoConfiguration+ComponentScan)的所有内容,即完整应用上下文。包括:

  • application.properties文件
  • 配置信息
  • 支持@Autowired注入的bean

4.3. 轻量级还是重量级

@SpringBootTest是重量级方案

  • 默认配置用于集成测试
  • 包含应用上下文中所有bean
  • 测试执行速度较慢

@WebMvcTest是轻量级方案

  • 仅关注MVC层,适合单元测试
  • 可限定测试特定控制器
  • 应用上下文中bean数量有限
  • 测试执行速度明显更快

4.4. 测试中的Web环境

真实应用通常通过http://localhost:8080访问,测试中使用webEnvironment模拟此场景并定义端口:

@SpringBootTest支持两种模式:

  • 模拟环境(WebEnvironment.MOCK
  • 真实环境(WebEnvironment.RANDOM_PORT

@WebMvcTest仅提供模拟测试环境

@SpringBootTest配合WebEnvironment的示例:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class SortingControllerWithWebEnvironmentIntegrationTest {
    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private ObjectMapper objectMapper;
}

GET方法测试用例:

@Test
void whenHelloWorldMethodIsCalled_thenReturnSuccessString() {
    ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:" + port + "/", String.class);
    Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());
    Assertions.assertEquals("Hello, World!", response.getBody());
}

POST方法测试用例:

@Test
void whenSortMethodIsCalled_thenReturnSortedArray() throws Exception {
    List<Integer> input = Arrays.asList(5, 3, 8, 1, 9, 2);
    List<Integer> sorted = Arrays.asList(1, 2, 3, 5, 8, 9);

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON);

    ResponseEntity<List> response = restTemplate.postForEntity("http://localhost:" + port + "/",
      new HttpEntity<>(objectMapper.writeValueAsString(input), headers),
      List.class);

    Assertions.assertEquals(HttpStatus.OK, response.getStatusCode());
    Assertions.assertEquals(sorted, response.getBody());
}

4.5. 依赖处理

@WebMvcTest不会自动检测控制器依赖,需手动模拟:

@WebMvcTest
class SortingControllerUnitTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private SortingService sortingService;
}

使用模拟bean的MockMvc测试示例:

@Test
void whenSortMethodIsCalled_thenReturnSortedArray() throws Exception {
    List<Integer> input = Arrays.asList(5, 3, 8, 1, 9, 2);
    List<Integer> sorted = Arrays.asList(1, 2, 3, 5, 8, 9);

    when(sortingService.sortArray(input)).thenReturn(sorted);
    mockMvc.perform(post("/").contentType(MediaType.APPLICATION_JSON)
      .content(objectMapper.writeValueAsString(input)))
      .andExpect(status().isOk())
      .andExpect(content().json(objectMapper.writeValueAsString(sorted)));
}

❌ 未模拟服务会导致NullPointerException

4.6. 自定义能力

@SpringBootTest自定义能力有限,而**@WebMvcTest可精确限定测试范围**:

@WebMvcTest(SortingController.class)
class SortingControllerUnitTest {
    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private SortingService sortingService;

    @Autowired
    private ObjectMapper objectMapper;
}

此配置仅注册SortingController及其依赖到应用上下文。

5. 总结

@SpringBootTest@WebMvcTest各有明确用途:

  • **@WebMvcTest**:

    • 专注MVC层测试
    • 提供特定控制器的便捷测试
    • 配合@MockBean模拟服务层
    • 执行速度快,适合单元测试
  • **@SpringBootTest**:

    • 加载完整应用上下文
    • 包含所有组件、数据库连接等
    • 适合集成测试和系统测试
    • 执行速度较慢

使用MockMvc时:

  • @SpringBootTest直接调用真实服务实现
  • @WebMvcTest通过@MockBean模拟服务响应

本文代码示例可在GitHub获取。


原始标题:Using MockMvc With SpringBootTest vs. Using WebMvcTest | Baeldung