1.环境准备
本文主要是介绍mock测试方面的知识,用到的环境是 idea + jdk8 + mysql5.5.49 + Junit5 + springBoot 2.6 + mokito
1.1数据库脚本准备
-- 创建订单表
CREATE TABLE `sale_order` (`id` bigint NOT null AUTO_INCREMENT COMMENT '客户订单ID',`sale_order_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单编号',`customer` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '客户',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `uni_sale_order_code` (`sale_order_code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='客户订单(销售订单)';-- 创建订单颜色明细表
CREATE TABLE `sale_order_detail` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '客户订单明细ID',`sale_order_id` bigint NOT NULL COMMENT '客户订单ID',`color` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL,`quantity` bigint DEFAULT NULL COMMENT '数量',`receiver_price` decimal(22,2) DEFAULT NULL COMMENT '接单价格',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='客户订单明细表(下单信息)';
1.2.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.2</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.fufulong.demo</groupId><artifactId>mock-demo</artifactId><version>0.0.1-SNAPSHOT</version><name>mock-demo</name><description>mock-demo</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.6.5</version></dependency><!-- https://mvnrepository.com/artifact/org.mockito/mockito-core --><dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>3.12.4</version><scope>test</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.75</version></dependency><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.2</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins><resources><resource><directory>src/main/resources</directory><includes><include>**/*.properties</include><include>**/*.xml</include><include>**/*.yml</include></includes><filtering>true</filtering></resource><resource><directory>src/main/resources</directory><includes><include>META-INF/**</include><include>template/*</include></includes><filtering>false</filtering></resource></resources></build></project>
1.3 配置文件准备
spring:application:name: mock-demodatasource:username: rootpassword: xxxxurl: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&rewriteBatchedStatements=trueservlet:multipart:max-request-size: 100MBmax-file-size: 10MBserver:port: 9000servlet:context-path: /mock-demo
2. mockMvc 的使用方法
2.1 初始化mockMvc
使用@SpringBootTest
注解会加载整个Context。这样我们可以自动获得所有在Context中注入的Bean,以及从application.properties中加载的配置信息。
在@SpringBootTest
中声明webEnvironment为WebEnvironment.MOCK
(默认值就是WebEnvironment.MOCK
)后,结合@AutoConfigureMockMvc
注解,在测试的时候会得到一个模拟的Web/Servlet环境。
因为没有Web Server,所以就无法使用RestTemplate
,也就只能继续使用MockMVC
了。这次MockMVC
的实例是由@AutoConfigureMockMvc
注解来完成的。这归功于SpringBoot的自动化配置。
junit5和junit4环境,初始化mocKMvc的方法不一样,如果是Junit5,初始化 mockMvc的代码如下:
@SpringBootTest(classes = MockDemoApplication.class)
@AutoConfigureMockMvc
class MockDemoApplicationTests {@Autowiredprivate MockMvc mockMvc;}
也可以使用另外一种方式,使用MockMvcBuilders 得静态方法初始化mockMvc,这样做还可以设置每次mock测试都要执行的动作,期望等
@SpringBootTest(classes = MockDemoApplication.class)
class MockDemoApplicationTests {private MockMvc mockMvc;@Autowiredprivate WebApplicationContext webApplicationContext;@BeforeEachpublic void setUp(){// 设定mockMvc能mock的controller是整个springBoot项目环境中的controllermockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext). build();}}
2.2 测试post类型的接口
接口方法
@RestController
@RequestMapping("saleOrder")
public class SaleOrderController {@Autowiredprivate SaleOrderService saleOrderService;@PostMapping(value = "/save")public DataResponse<Long> save(@RequestBody SaveSaleOrderReq req){Long id = saleOrderService.save(req);return DataResponse.ok(id);}}
测试方法
@Test
public void test1() throws Exception {SaleOrder saveOrder = new SaleOrder();saveOrder.setSaleOrderCode("0001");saveOrder.setCustomer("小明");MockHttpServletRequestBuilder requestBuilder =//请求路径,不需要带前面 servletContext部分MockMvcRequestBuilders.post("/saleOrder/save")// post请求返回的mediaType.accept(MediaType.APPLICATION_JSON)// post请求的请求内容的 mediaType.contentType(MediaType.APPLICATION_JSON)// post 请求参数内容.content(JSON.toJSONString(saveOrder));MvcResult mvcResult = mockMvc.perform(requestBuilder)// 设置期望的结果,用 ResultMatcher 来表示.andExpect(MockMvcResultMatchers.status().isOk())// 内置的打印mock请求结果.andDo(MockMvcResultHandlers.print())// 正式执行接口,并返回接口的返回值.andReturn();// 把 mvcResult 的 response对象,转换成 json,然后打印显示String s = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);System.out.println(s);
}
结果如下所示:
MockHttpServletRequest:HTTP Method = POSTRequest URI = /saleOrder/saveParameters = {}Headers = [Content-Type:"application/json", Accept:"application/json", Content-Length:"44"]Body = <no character encoding set>Session Attrs = {}Handler:Type = com.fufulong.demo.mockdemo.controller.SaleOrderControllerMethod = com.fufulong.demo.mockdemo.controller.SaleOrderController#save(SaveSaleOrderReq)Async:Async started = falseAsync result = nullResolved Exception:Type = nullModelAndView:View name = nullView = nullModel = nullFlashMap:Attributes = nullMockHttpServletResponse:Status = 200Error message = nullHeaders = [Content-Type:"application/json"]Content type = application/jsonBody = {"successful":true,"code":"200","message":null,"data":1}Forwarded URL = nullRedirected URL = nullCookies = []
{"successful":true,"code":"200","message":null,"data":1}
2.3 测试get类型的接口 (requestParam参数类型)
接口方法
@GetMapping(value = "/get-one")
public DataResponse<SaleOrder> getOne(@RequestParam(value = "saleOrderId") Long saleOrderId){SaleOrder saleOrder = saleOrderService.selectOne(saleOrderId);return DataResponse.ok(saleOrder);
}
测试方法
@Test
public void test2() throws Exception {MockHttpServletRequestBuilder requestBuilder =//请求路径,不需要带前面 servletContext部分MockMvcRequestBuilders.get("/saleOrder/get-one")// 设置请求参数.param("saleOrderId","1");MvcResult mvcResult = mockMvc.perform(requestBuilder)// 设置期望的结果,用 ResultMatcher 来表示.andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();String s = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);System.out.println(s);}
测试结果输出
MockHttpServletRequest:HTTP Method = GETRequest URI = /saleOrder/get-oneParameters = {saleOrderId=[1]}Headers = []Body = <no character encoding set>Session Attrs = {}Handler:Type = com.fufulong.demo.mockdemo.controller.SaleOrderControllerMethod = com.fufulong.demo.mockdemo.controller.SaleOrderController#getOne(Long)Async:Async started = falseAsync result = nullResolved Exception:Type = nullModelAndView:View name = nullView = nullModel = nullFlashMap:Attributes = nullMockHttpServletResponse:Status = 200Error message = nullHeaders = [Content-Type:"application/json"]Content type = application/jsonBody = {"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"å°æ˜Ž"}}Forwarded URL = nullRedirected URL = nullCookies = []
{"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"小明"}}
2.4 测试get类型的接口 (path参数类型)
接口代码
@GetMapping(value = "/get-one/{saleOrderCode}/{id}")
public DataResponse<SaleOrder> getOneByCode(@PathVariable(value = "saleOrderCode") String saleOrderCode,@PathVariable(value = "id") Long id ){SaleOrder saleOrder = saleOrderService.getOneByCode(saleOrderCode,id);return DataResponse.ok(saleOrder);
}
测试方法
@Test
public void test3() throws Exception {String saleOrderCode = "0001";Long id = 1L;MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/saleOrder/get-one/{saleOrderCode}/{id}",saleOrderCode,id);MvcResult mvcResult = mockMvc.perform(requestBuilder)// 设置期望的结果,用 ResultMatcher 来表示.andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();String s = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);System.out.println(s);
}
测试结果打印
MockHttpServletRequest:HTTP Method = GETRequest URI = /saleOrder/get-one/0001/1Parameters = {}Headers = []Body = <no character encoding set>Session Attrs = {}Handler:Type = com.fufulong.demo.mockdemo.controller.SaleOrderControllerMethod = com.fufulong.demo.mockdemo.controller.SaleOrderController#getOneByCode(String, Long)Async:Async started = falseAsync result = nullResolved Exception:Type = nullModelAndView:View name = nullView = nullModel = nullFlashMap:Attributes = nullMockHttpServletResponse:Status = 200Error message = nullHeaders = [Content-Type:"application/json"]Content type = application/jsonBody = {"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"å°æ˜Ž"}}Forwarded URL = nullRedirected URL = nullCookies = []
{"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"小明"}}
2.5 测试上传文件
接口代码
@RequestMapping(value = "/uploadFile")public DataResponse<Void> uploadFile(@RequestParam(value = "file") MultipartFile file) throws IOException {InputStream inputStream = null;OutputStream outputStream = null;try{inputStream = file.getInputStream();outputStream = FileUtil.getOutputStream(new File(Objects.requireNonNull(file.getOriginalFilename())));IoUtil.copy(inputStream,outputStream);}finally {if (inputStream != null){inputStream.close();}if (outputStream != null){outputStream.close();}}return DataResponse.ok();}
测试代码
@Testpublic void test4() throws Exception {File file = new File("C:\\Users\\付福龙\\Desktop\\兔子.jpeg");MockMultipartFile uploadFile = new MockMultipartFile("file", "兔子.jpeg", MediaType.IMAGE_JPEG_VALUE, new FileInputStream(file));MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.multipart("/saleOrder/uploadFile").file(uploadFile).contentType( MediaType.IMAGE_JPEG);MvcResult mvcResult = mockMvc.perform(requestBuilder)// 设置期望的结果,用 ResultMatcher 来表示.andExpect(MockMvcResultMatchers.status().isOk()).andDo(MockMvcResultHandlers.print()).andReturn();}
测试结果
MockHttpServletRequest:HTTP Method = POSTRequest URI = /saleOrder/uploadFileParameters = {}Headers = [Content-Type:"image/jpeg"]Body = <no character encoding set>Session Attrs = {}Handler:Type = com.fufulong.demo.mockdemo.controller.SaleOrderControllerMethod = com.fufulong.demo.mockdemo.controller.SaleOrderController#uploadFile(MultipartFile)Async:Async started = falseAsync result = nullResolved Exception:Type = nullModelAndView:View name = nullView = nullModel = nullFlashMap:Attributes = nullMockHttpServletResponse:Status = 200Error message = nullHeaders = [Content-Type:"application/json"]Content type = application/jsonBody = {"successful":true,"code":"200","message":null,"data":null}Forwarded URL = nullRedirected URL = nullCookies = []
3.mockMvc测试controller总结
- 先有业务接口方法
- 新建的测试类中,需要先初始化mockMvc对象
- 根据接口方法的类型不同(post/get/multipart等),选择构建不同的MockHttpServletRequestBuilder 对象, 在这里设置请求地址,注意地址是不需要 地址端口和servletContextpath前缀的,且需要注意用"/"开头. 然后设置 queryParam或者pathParam或者 content,还可以设置请求头等
- 调用mockMvc的方法,指定需要执行的请求, mockMvc.perform(MockHttpServletRequestBuilder )
- 然后设置断言,断言可以设置多个,使用方法 andExpect(ResultMatcher matcher) 设置, MockMvcResultMatchers中有许多常用的断言结果匹配器,可以使用
- 然后设置结果处理方法, 使用方法 ResultActions andDo(ResultHandler handler) 来处理,同样 MockMvcResultHandlers 总有需要封装好的静态方法可以用
- 然后 调用方法 MvcResult andReturn() ,此时才会真的执行接口, 得到 MvcResult 结果
- 可以使用 mvcResult.getResponse() 得到 MockHttpServletResponse 对象, 这个对象有许多请求结果的信息,比如状态,内容等, 可以根据这些内容写后续的验证代码
4. mockMvc测试重要API
41. MockMvcRequestBuilders
- MockMvcRequestBuildersMockHttpServletRequestBuilder get(String urlTemplate, Object… uriVars)
构建 get请求,设置请求地址,并且可以设置路径参数,注意如果要设置路径参数,参数值和类型必须要对应地址中参数名 - MockHttpServletRequestBuilder post(String urlTemplate, Object… uriVars)
构建 post 请求, 设置请求地址, post方法虽然也可以设置 pathParam和queryParam,但是不建议这样做. - MockMultipartHttpServletRequestBuilder multipart(String urlTemplate, Object… uriVars)
构建 上传文件 请求, 设置上传文件的接口地址和路径参数等.
4.2 MockMvcRequestBuilder
-
MockHttpServletRequestBuilder param(String name, String… values)
向请求参数map中添加非path类型的参数,也就是设置 queryParam的方法
-
MockHttpServletRequestBuilder header(String name, Object… values)
向请求头map中添加请求头键值对 -
MockHttpServletRequestBuilder contentType(MediaType contentType)
设置接口的参数内容的 contentType,如果业务接口是 @requestController 修饰的, contentType 一般是MediaType.APPLICATION_JSON -
MockHttpServletRequestBuilder accept(MediaType… mediaTypes)
设置接口的可以接收的返回数据的MediaType, 如果业务接口是 @requestController 修饰的, 一般是MediaType.APPLICATION_JSON -
MockHttpServletRequestBuilder content(String content)
设置requestBody的内容,一般是post请求的时候需要设置
4.3 ResultActions
- ResultActions andExpect(ResultMatcher matcher)
设置请求相关的断言,可以连续调用多次设置不同的断言. - ResultActions andDo(ResultHandler handler)
设置对接口返回结果的处理方式 - MvcResult andReturn()
执行测试接口,得到结果,返回数据
4.4 MockMvcResultMatchers
- StatusResultMatchers status(): 得到结果状态匹配器, 可以使用StatusResultMatchers 的方法断言请求状态,请求是否正常返回等.
- ModelResultMatchers model(): 当请求不是rest风格的时候,返回接口结果中的数据model,可用进一步通过调用 ModelResultMatchers 的静态方法,判断返回的数据中的属性的值等
- ViewResultMatchers view(): 当请求不是rest风格的时候,返回接口结果中的视图地址匹配器
- JsonPathResultMatchers jsonPath(String expression, Object… args) / ResultMatcher jsonPath(String expression, Matcher<? super T> matcher) / ResultMatcher jsonPath(String expression, Matcher<? super T> matcher, Class targetType)
当接口返回的数据是Json的形式,可以用这3个方法设置断言匹配器, 关于 jsoPath的设置,比较复杂,可以看官网地址: https://github.com/json-path/JsonPath
4.5 MockMvcResultHandlers
这里的方法主要是打印或者日志记录mock测试的主要信息,注意如果是打印到系统的控制台,使用的是系统的编码方式,如果是wIndows系统,就是ISO-8859-1,那么输出的中文就是乱码了.
4.6 MockHttpServletResponse
- String getContentAsString(Charset fallbackCharset)
把接口返回的数据内容转换成字符串形式,并指定了编码方式