SpringBoot基础之MockMvc单元测试

SpringBoot创建的Maven项目中,会默认添加spring-boot-starter-test依赖。在《5分钟快速上手SpringBoot》中编写的单元测试使用了MockMvc。本篇文章就围绕MockMvc在SpringBoot中的使用进行讲解。

什么是Mock

在面向对象的程序设计中,模拟对象(英语:mock object)是以可控的方式模拟真实对象行为的假对象。在编程过程中,通常通过模拟一些输入数据,来验证程序是否达到预期结果。

为什么使用Mock对象

使用模拟对象,可以模拟复杂的、真实的对象行为。如果在单元测试中无法使用真实对象,可采用模拟对象进行替代。

在以下情况可以采用模拟对象来替代真实对象:

  • 真实对象的行为是不确定的(例如,当前的时间或温度);
  • 真实对象很难搭建起来;
  • 真实对象的行为很难触发(例如,网络错误);
  • 真实对象速度很慢(例如,一个完整的数据库,在测试之前可能需要初始化);
  • 真实的对象是用户界面,或包括用户界面在内;
  • 真实的对象使用了回调机制;
  • 真实对象可能还不存在;
  • 真实对象可能包含不能用作测试(而不是为实际工作)的信息和方法。

使用Mockito一般分三个步骤:1、模拟测试类所需的外部依赖;2、执行测试代码;3、判断执行结果是否达到预期;

MockMvc

MockMvc是由spring-test包提供,实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,使得测试速度快、不依赖网络环境。同时提供了一套验证的工具,结果的验证十分方便。

接口MockMvcBuilder,提供一个唯一的build方法,用来构造MockMvc。主要有两个实现:StandaloneMockMvcBuilder和DefaultMockMvcBuilder,分别对应两种测试方式,即独立安装和集成Web环境测试(并不会集成真正的web环境,而是通过相应的Mock API进行模拟测试,无须启动服务器)。MockMvcBuilders提供了对应的创建方法standaloneSetup方法和webAppContextSetup方法,在使用时直接调用即可。

SpringBoot中使用

第一步:jar包引入。创建SpringBoot项目中默认引入的spring-boot-starter-test间接引入了spring-test,因此无需再额外引入jar包。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

第二步:创建HelloWorldController类,并提供hello方法作为待测试的业务接口。

@RestController
public class HelloWorldController {

	@RequestMapping
	public String hello(String name){
		return "Hello " + name + "!";
	}
}

第三步:编写测试类。实例化MockMvc有两种形式,一种是使用StandaloneMockMvcBuilder,另外一种是使用DefaultMockMvcBuilder。测试类及初始化MockMvc初始化:

//SpringBoot1.4版本之前用的是SpringJUnit4ClassRunner.class
@RunWith(SpringRunner.class)
//SpringBoot1.4版本之前用的是@SpringApplicationConfiguration(classes = Application.class)
@SpringBootTest
//测试环境使用,用来表示测试环境使用的ApplicationContext将是WebApplicationContext类型的
@WebAppConfiguration
public class HelloWorldTest {

	private MockMvc mockMvc;

	@Autowired
	private WebApplicationContext webApplicationContext;

	@Before
	public void setup() {
		// 实例化方式一
		mockMvc = MockMvcBuilders.standaloneSetup(new HelloWorldController()).build();
		// 实例化方式二
//		mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
	}

单元测试方法:

@Test
public void testHello() throws Exception {

	/*
	 * 1、mockMvc.perform执行一个请求。
	 * 2、MockMvcRequestBuilders.get("XXX")构造一个请求。
	 * 3、ResultActions.param添加请求传值
	 * 4、ResultActions.accept(MediaType.TEXT_HTML_VALUE))设置返回类型
	 * 5、ResultActions.andExpect添加执行完成后的断言。
	 * 6、ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情
	 *   比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。
	 * 7、ResultActions.andReturn表示执行完成后返回相应的结果。
	 */
	mockMvc.perform(MockMvcRequestBuilders
			.get("/hello")
			// 设置返回值类型为utf-8,否则默认为ISO-8859-1
			.accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
			.param("name", "Tom"))
			.andExpect(MockMvcResultMatchers.status().isOk())
			.andExpect(MockMvcResultMatchers.content().string("Hello Tom!"))
			.andDo(MockMvcResultHandlers.print());
}

测试结果打印:

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"10"]
     Content type = application/json;charset=UTF-8
             Body = Hello Tom!
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
2019-04-02 21:34:27.954  INFO 6937 --- [       Thread-2] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'

整个过程如下:
1、准备测试环境
2、通过MockMvc执行请求
3、添加验证断言
4、添加结果处理器
5、得到MvcResult进行自定义断言/进行下一步的异步请求
6、卸载测试环境

注意事项:如果使用DefaultMockMvcBuilder进行MockMvc实例化时需在SpringBoot启动类上添加组件扫描的package的指定,否则会出现404。如:

@ComponentScan(basePackages = "com.secbro2")

相关API

RequestBuilder提供了一个方法buildRequest(ServletContext servletContext)用于构建MockHttpServletRequest;其主要有两个子类MockHttpServletRequestBuilder和MockMultipartHttpServletRequestBuilder(如文件上传使用)。

MockMvcRequestBuilders提供get、post等多种方法用来实例化RequestBuilder。

ResultActions,MockMvc.perform(RequestBuilder requestBuilder)的返回值,提供三种能力:andExpect,添加断言判断结果是否达到预期;andDo,添加结果处理器,比如示例中的打印;andReturn,返回验证成功后的MvcResult,用于自定义验证/下一步的异步处理。

一些常用的测试

1.测试普通控制器

mockMvc.perform(get("/user/{id}", 1)) //执行请求  
            .andExpect(model().attributeExists("user")) //验证存储模型数据  
            .andExpect(view().name("user/view")) //验证viewName  
            .andExpect(forwardedUrl("/WEB-INF/jsp/user/view.jsp"))//验证视图渲染时forward到的jsp  
            .andExpect(status().isOk())//验证状态码  
            .andDo(print()); //输出MvcResult到控制台

2.得到MvcResult自定义验证

MvcResult result = mockMvc.perform(get("/user/{id}", 1))//执行请求  
        .andReturn(); //返回MvcResult  
Assert.assertNotNull(result.getModelAndView().getModel().get("user")); //自定义断言 

3.验证请求参数绑定到模型数据及Flash属性

mockMvc.perform(post("/user").param("name", "zhang")) //执行传递参数的POST请求(也可以post("/user?name=zhang"))  
            .andExpect(handler().handlerType(UserController.class)) //验证执行的控制器类型  
            .andExpect(handler().methodName("create")) //验证执行的控制器方法名  
            .andExpect(model().hasNoErrors()) //验证页面没有错误  
            .andExpect(flash().attributeExists("success")) //验证存在flash属性  
            .andExpect(view().name("redirect:/user")); //验证视图  

4.文件上传

byte[] bytes = new byte[] {1, 2};  
mockMvc.perform(fileUpload("/user/{id}/icon", 1L).file("icon", bytes)) //执行文件上传  
        .andExpect(model().attribute("icon", bytes)) //验证属性相等性  
        .andExpect(view().name("success")); //验证视图 

5.JSON请求/响应验证

String requestBody = "{\"id\":1, \"name\":\"zhang\"}";  
    mockMvc.perform(post("/user")  
            .contentType(MediaType.APPLICATION_JSON).content(requestBody)  
            .accept(MediaType.APPLICATION_JSON)) //执行请求  
            .andExpect(content().contentType(MediaType.APPLICATION_JSON)) //验证响应contentType  
            .andExpect(jsonPath("$.id").value(1)); //使用Json path验证JSON 请参考http://goessner.net/articles/JsonPath/  
      
    String errorBody = "{id:1, name:zhang}";  
    MvcResult result = mockMvc.perform(post("/user")  
            .contentType(MediaType.APPLICATION_JSON).content(errorBody)  
            .accept(MediaType.APPLICATION_JSON)) //执行请求  
            .andExpect(status().isBadRequest()) //400错误请求  
            .andReturn();  
      
    Assert.assertTrue(HttpMessageNotReadableException.class.isAssignableFrom(result.getResolvedException().getClass()));//错误的请求内容体

6.异步测试

//Callable  
    MvcResult result = mockMvc.perform(get("/user/async1?id=1&name=zhang")) //执行请求  
            .andExpect(request().asyncStarted())  
            .andExpect(request().asyncResult(CoreMatchers.instanceOf(User.class))) //默认会等10秒超时  
            .andReturn();  
      
    mockMvc.perform(asyncDispatch(result))  
            .andExpect(status().isOk())  
            .andExpect(content().contentType(MediaType.APPLICATION_JSON))  
            .andExpect(jsonPath("$.id").value(1));  

7.全局配置

mockMvc = webAppContextSetup(wac)  
            .defaultRequest(get("/user/1").requestAttr("default", true)) //默认请求 如果其是Mergeable类型的,会自动合并的哦mockMvc.perform中的RequestBuilder  
            .alwaysDo(print())  //默认每次执行请求后都做的动作  
            .alwaysExpect(request().attribute("default", true)) //默认每次执行后进行验证的断言  
            .build();  
      
    mockMvc.perform(get("/user/1"))  
            .andExpect(model().attributeExists("user"));

关注微信公众:程序新视界,回复“mockmvc”即可获得本篇内容PDF版本,更多免费资料,期待你的到来。

Spring技术视频

CSDN学院:《Spring Boot 视频教程全家桶》


程序新视界

关注程序员的职场生涯,大量优质学习资源

csdn-微信公众号

<think>好的,我现在要解决用户的问题:如何在Spring Boot中对Controller进行单元测试,并且需要给出示例。首先,我需要回顾一下用户提供的引用内容,看看有没有相关的信息可以利用。 用户提供的引用3和4都提到了Controller层的测试。引用3展示了一个Controller类的代码,里面有GET和POST方法的示例。引用4则提到了使用MockMvc和doAnswer来模拟响应,这可能涉及到如何模拟依赖的行为。另外,引用1和2主要讲的是Spring Boot单元测试基础配置,比如@SpringBootTest和@Autowired的使用,这些是测试Controller的基础。 接下来,我需要整理一下Spring Boot中测试Controller的常用方法。通常有两种方式:一种是使用@SpringBootTest注解启动完整的Spring上下文,结合MockMvc进行集成测试;另一种是使用WebMvcTest进行切片测试,只加载Controller相关的上下文,并配合MockBean来模拟Service层。这两种方法各有优劣,用户可能需要知道这两种方式。 用户请求的是示例,所以需要给出具体的代码示例。比如,针对引用3中的WebController,如何编写测试用例。同时要考虑到不同的HTTP方法(GET、POST)以及如何处理请求体和请求头。此外,是否需要模拟Service层,比如使用Mockito来mock依赖,这样测试可以更专注在Controller的逻辑上,而不是整个应用流程。 然后,我需要确保代码示例符合用户的要求,比如使用正确的注解,如@RunWith(SpringRunner.class)或者@SpringBootTest,以及如何配置MockMvc。引用2中的CalculatorServiceTest示例可能对如何编写测试方法有帮助,但需要调整到Controller的测试场景。 另外,用户可能关心测试的独立性和速度,所以使用WebMvcTest可能更适合单元测试,因为它只加载部分上下文,启动更快。而@SpringBootTest适合集成测试,但启动时间较长。需要根据具体情况建议。 还要注意测试中的断言,比如检查HTTP状态码、响应内容是否正确,以及是否处理了请求参数和请求体。引用4中提到的doAnswer和断言的使用,可能在模拟DAO层时有用,但在Controller测试中,可能更多使用MockMvc的perform和andExpect方法。 最后,要确保回答结构清晰,分步骤讲解,代码示例正确,并且符合用户提到的格式要求,比如行内数学公式用$符号,但这里可能用不到数学公式。不过需要检查是否有需要LaTeX的地方,比如没有的话就不用加。 总结步骤: 1. 介绍两种测试方式:集成测试(@SpringBootTest + MockMvc)和单元测试(@WebMvcTest + MockBean)。 2. 分别给出两种方式的示例代码,比如测试GET和POST方法。 3. 解释代码中的关键注解和方法,如@AutoConfigureMockMvc,@MockBean,MockMvc的perform、andExpect等。 4. 可能提到如何模拟Service层,使用Mockito.when来处理依赖方法。 5. 强调测试的覆盖点和断言的重要性。 需要验证代码是否正确,比如是否正确处理了@RequestHeader,如何传递JSON内容,以及使用MockMvc的请求构建方法。同时,确保引用的内容被正确参考,比如引用3中的WebController,在测试示例中是否对应。 可能的问题点:是否需要启动完整的Spring上下文?如果使用@WebMvcTest,是否需要指定Controller类?是的,通常@WebMvcTest(WebController.class)来限定只加载该Controller的配置。 最后,生成相关问题,帮助用户进一步了解,比如如何模拟依赖、如何处理异常测试、如何测试文件上传等。</think>在Spring Boot中对Controller进行单元测试,主要使用`MockMvc`模拟HTTP请求,并结合`@WebMvcTest`或`@SpringBootTest`注解。以下是具体实现步骤和示例: --- ### 方法一:使用@WebMvcTest(推荐用于纯Controller层测试) 仅加载Controller相关配置,配合`@MockBean`模拟Service依赖,适合快速单元测试。 ```java import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebMvcTest(WebController.class) // 指定测试的Controller类 public class WebControllerTest { @Autowired private MockMvc mockMvc; @MockBean // 模拟Service依赖 private MyService myService; @Test public void testGet() throws Exception { mockMvc.perform(get("/web/get")) // 模拟GET请求 .andExpect(status().isOk()) // 断言HTTP状态码 .andExpect(content().string("get")); // 断言响应内容 } @Test public void testPost() throws Exception { String requestBody = "{\"name\":\"Alice\",\"age\":20}"; mockMvc.perform(post("/web/post") .contentType("application/json") .content(requestBody)) .andExpect(status().isOk()) .andExpect(content().string("post success")); } @Test public void testPostWithHeader() throws Exception { mockMvc.perform(post("/web/postHead") .header("token", "abc123")) // 设置请求头 .andExpect(status().isOk()) .andExpect(content().string("post head token:abc123")); } } ``` --- ### 方法二:使用@SpringBootTest(集成测试) 启动完整Spring上下文,适合需要测试完整调用链的场景。 ```java import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; @SpringBootTest @AutoConfigureMockMvc // 自动配置MockMvc public class WebControllerIntegrationTest { @Autowired private MockMvc mockMvc; @Test public void testGetIntegration() throws Exception { mockMvc.perform(get("/web/get")) .andExpect(status().isOk()); } } ``` --- ### 关键注解说明 1. **`@WebMvcTest`** 仅初始化Controller层,需配合`@MockBean`模拟依赖[^3]。 2. **`@SpringBootTest`** 加载完整Spring上下文,适合集成测试[^2]。 3. **`MockMvc`** 提供`perform()`方法模拟HTTP请求,支持链式断言[^4]。 4. **`@MockBean`** 替换Spring上下文中的实际Bean为Mock对象,例如: ```java @MockBean private UserService userService; when(userService.findById(1L)).thenReturn(new User("Test")); // Mock方法调用 ``` --- ### 测试覆盖场景 1. **请求参数验证** 使用`param()`方法传递URL参数: ```java mockMvc.perform(get("/api/user").param("id", "1")) ``` 2. **JSON请求体处理** 通过`content()`传递JSON字符串,并设置`contentType("application/json")`。 3. **异常测试** 断言特定异常状态码: ```java .andExpect(status().isBadRequest()) ``` ---
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序新视界

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值