本文主要是介绍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-demo datasource: username: root password: xxxx url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&rewriteBatchedStatements=true servlet: multipart: max-request-size: 100MB max-file-size: 10MB server: port: 9000 servlet: context-path: /mock-demo
使用@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 { @Autowired private MockMvc mockMvc; }
也可以使用另外一种方式,使用MockMvcBuilders 得静态方法初始化mockMvc,这样做还可以设置每次mock测试都要执行的动作,期望等
@SpringBootTest(classes = MockDemoApplication.class) class MockDemoApplicationTests { private MockMvc mockMvc; @Autowired private WebApplicationContext webApplicationContext; @BeforeEach public void setUp(){ // 设定mockMvc能mock的controller是整个springBoot项目环境中的controller mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext). build(); } }
接口方法
@RestController @RequestMapping("saleOrder") public class SaleOrderController { @Autowired private 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 = POST Request URI = /saleOrder/save Parameters = {} 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.SaleOrderController Method = com.fufulong.demo.mockdemo.controller.SaleOrderController#save(SaveSaleOrderReq) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"successful":true,"code":"200","message":null,"data":1} Forwarded URL = null Redirected URL = null Cookies = [] {"successful":true,"code":"200","message":null,"data":1}
接口方法
@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 = GET Request URI = /saleOrder/get-one Parameters = {saleOrderId=[1]} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.fufulong.demo.mockdemo.controller.SaleOrderController Method = com.fufulong.demo.mockdemo.controller.SaleOrderController#getOne(Long) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"å°æ˜Ž"}} Forwarded URL = null Redirected URL = null Cookies = [] {"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"小明"}}
接口代码
@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 = GET Request URI = /saleOrder/get-one/0001/1 Parameters = {} Headers = [] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.fufulong.demo.mockdemo.controller.SaleOrderController Method = com.fufulong.demo.mockdemo.controller.SaleOrderController#getOneByCode(String, Long) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"å°æ˜Ž"}} Forwarded URL = null Redirected URL = null Cookies = [] {"successful":true,"code":"200","message":null,"data":{"id":1,"saleOrderCode":"0001","customer":"小明"}}
接口代码
@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(); }
测试代码
@Test public 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 = POST Request URI = /saleOrder/uploadFile Parameters = {} Headers = [Content-Type:"image/jpeg"] Body = <no character encoding set> Session Attrs = {} Handler: Type = com.fufulong.demo.mockdemo.controller.SaleOrderController Method = com.fufulong.demo.mockdemo.controller.SaleOrderController#uploadFile(MultipartFile) Async: Async started = false Async result = null Resolved Exception: Type = null ModelAndView: View name = null View = null Model = null FlashMap: Attributes = null MockHttpServletResponse: Status = 200 Error message = null Headers = [Content-Type:"application/json"] Content type = application/json Body = {"successful":true,"code":"200","message":null,"data":null} Forwarded URL = null Redirected URL = null Cookies = []
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请求的时候需要设置
这里的方法主要是打印或者日志记录mock测试的主要信息,注意如果是打印到系统的控制台,使用的是系统的编码方式,如果是wIndows系统,就是ISO-8859-1,那么输出的中文就是乱码了.