Skip to content

MockMvcBuilders.webAppContextSetup(WebApplicationContext).build() 详解

一、代码段解析

MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build() 这行代码是Spring测试框架中创建MockMvc实例的标准方式,其作用如下:

  1. MockMvcBuilders:工厂类,用于构建MockMvc实例
  2. webAppContextSetup():指定使用完整的Web应用上下文来构建MockMvc
  3. webApplicationContext:已加载的Spring Web应用上下文
  4. build():最终构建出MockMvc实例

二、MockMvc类的作用

MockMvc是Spring MVC测试框架的核心类,主要提供以下功能:

1. 模拟HTTP请求

  • 支持所有HTTP方法(GET/POST/PUT/DELETE等)
  • 可以模拟请求头、参数、cookie、session等
  • 支持文件上传等复杂请求

2. 验证响应

  • 响应状态码验证
  • 响应头验证
  • 响应内容验证(JSON/XML/HTML等)
  • 重定向验证

3. 测试优势

  • 不需要启动真实服务器
  • 快速执行控制器测试
  • 支持完整的Spring MVC流程

三、webAppContextSetup详解

1. 工作流程

mermaid
graph TD
    A[加载WebApplicationContext] --> B[初始化DispatcherServlet]
    B --> C[配置所有已注册的Filter]
    C --> D[构建MockMvc实例]
graph TD
    A[加载WebApplicationContext] --> B[初始化DispatcherServlet]
    B --> C[配置所有已注册的Filter]
    C --> D[构建MockMvc实例]

2. 包含的组件

  • 所有@Controller@RestController bean
  • 所有配置的拦截器(Interceptors)
  • 所有注册的过滤器(Filters)
  • 消息转换器(Message Converters)
  • 视图解析器(View Resolvers)

3. 与standaloneSetup对比

特性webAppContextSetupstandaloneSetup
上下文完整Web应用上下文仅指定控制器的模拟上下文
自动配置包含所有自动配置的Bean需要手动配置依赖
过滤器/拦截器自动包含需要手动添加
使用场景集成测试单元测试
启动速度较慢较快

四、实际应用示例

1. 基础使用

java
@SpringBootTest
public class UserControllerTest {

    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mockMvc;
    
    @BeforeEach
    void setup() {
        mockMvc = MockMvcBuilders
            .webAppContextSetup(context)
            .alwaysDo(print()) // 总是打印请求响应信息
            .build();
    }
    
    @Test
    void getUser() throws Exception {
        mockMvc.perform(get("/users/1")
               .accept(MediaType.APPLICATION_JSON))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("John"));
    }
}
@SpringBootTest
public class UserControllerTest {

    @Autowired
    private WebApplicationContext context;
    
    private MockMvc mockMvc;
    
    @BeforeEach
    void setup() {
        mockMvc = MockMvcBuilders
            .webAppContextSetup(context)
            .alwaysDo(print()) // 总是打印请求响应信息
            .build();
    }
    
    @Test
    void getUser() throws Exception {
        mockMvc.perform(get("/users/1")
               .accept(MediaType.APPLICATION_JSON))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("John"));
    }
}

2. 高级配置

java
@BeforeEach
void setup() {
    mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .addFilter(new CharacterEncodingFilter("UTF-8", true)) // 添加过滤器
        .defaultRequest(get("/").contextPath("/api")) // 默认请求配置
        .alwaysExpect(content().contentType(MediaType.APPLICATION_JSON)) // 全局断言
        .build();
}
@BeforeEach
void setup() {
    mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .addFilter(new CharacterEncodingFilter("UTF-8", true)) // 添加过滤器
        .defaultRequest(get("/").contextPath("/api")) // 默认请求配置
        .alwaysExpect(content().contentType(MediaType.APPLICATION_JSON)) // 全局断言
        .build();
}

五、核心API详解

1. 请求构建

java
mockMvc.perform(
    post("/users") // HTTP方法+路径
    .contentType(MediaType.APPLICATION_JSON) // 内容类型
    .content("{\"name\":\"John\"}") // 请求体
    .header("X-Auth", "token") // 自定义头
    .cookie(new Cookie("test", "value")) // Cookie
    .sessionAttr("user", testUser) // Session属性
);
mockMvc.perform(
    post("/users") // HTTP方法+路径
    .contentType(MediaType.APPLICATION_JSON) // 内容类型
    .content("{\"name\":\"John\"}") // 请求体
    .header("X-Auth", "token") // 自定义头
    .cookie(new Cookie("test", "value")) // Cookie
    .sessionAttr("user", testUser) // Session属性
);

2. 结果验证

java
.andExpect(status().isCreated()) // 状态码
.andExpect(header().string("Location", "/users/123")) // 响应头
.andExpect(jsonPath("$.id").value(123)) // JSON路径
.andExpect(content().string(containsString("success"))) // 内容包含
.andExpect(view().name("userView")) // 视图名称
.andExpect(model().attributeExists("user")) // 模型属性
.andExpect(status().isCreated()) // 状态码
.andExpect(header().string("Location", "/users/123")) // 响应头
.andExpect(jsonPath("$.id").value(123)) // JSON路径
.andExpect(content().string(containsString("success"))) // 内容包含
.andExpect(view().name("userView")) // 视图名称
.andExpect(model().attributeExists("user")) // 模型属性

3. 结果处理

java
.andDo(print()) // 打印请求响应详情
.andDo(document("user-create")) // Spring REST Docs文档生成
.andReturn(); // 获取完整MvcResult
.andDo(print()) // 打印请求响应详情
.andDo(document("user-create")) // Spring REST Docs文档生成
.andReturn(); // 获取完整MvcResult

六、实际应用场景

1. 测试REST API

java
@Test
void createUser() throws Exception {
    String userJson = "{\"username\":\"test\",\"email\":\"test@example.com\"}";
    
    mockMvc.perform(post("/api/users")
           .contentType(MediaType.APPLICATION_JSON)
           .content(userJson))
           .andExpect(status().isCreated())
           .andExpect(jsonPath("$.id").exists());
}
@Test
void createUser() throws Exception {
    String userJson = "{\"username\":\"test\",\"email\":\"test@example.com\"}";
    
    mockMvc.perform(post("/api/users")
           .contentType(MediaType.APPLICATION_JSON)
           .content(userJson))
           .andExpect(status().isCreated())
           .andExpect(jsonPath("$.id").exists());
}

2. 测试表单提交

java
@Test
void submitForm() throws Exception {
    mockMvc.perform(post("/form")
           .param("name", "John")
           .param("age", "30"))
           .andExpect(status().isOk())
           .andExpect(model().attribute("result", "success"));
}
@Test
void submitForm() throws Exception {
    mockMvc.perform(post("/form")
           .param("name", "John")
           .param("age", "30"))
           .andExpect(status().isOk())
           .andExpect(model().attribute("result", "success"));
}

3. 测试文件上传

java
@Test
void uploadFile() throws Exception {
    MockMultipartFile file = new MockMultipartFile(
        "file", "test.txt", "text/plain", "content".getBytes());
    
    mockMvc.perform(multipart("/upload").file(file))
           .andExpect(status().isOk());
}
@Test
void uploadFile() throws Exception {
    MockMultipartFile file = new MockMultipartFile(
        "file", "test.txt", "text/plain", "content".getBytes());
    
    mockMvc.perform(multipart("/upload").file(file))
           .andExpect(status().isOk());
}

七、最佳实践

  1. 测试隔离:每个测试方法应该独立,使用@Transactional@Rollback
  2. 合理断言:优先验证业务逻辑,而非实现细节
  3. 日志输出:使用andDo(print())辅助调试
  4. 共享配置:抽象基础测试类减少重复代码
  5. 性能考虑:对于简单测试,考虑使用standaloneSetup

通过这种方式创建的MockMvc实例能够完整模拟Web容器环境,同时保持测试的高效执行,是Spring MVC控制器测试的首选方式。