简介
对模块进行集成测试时,希望能够通过输入URL对Controller进行测试,如果通过启动服务器,建立http client进行测试,这样会使得测试变得很麻烦,比如,启动速度慢,测试验证不方便,依赖网络环境等
MockMvc实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,而且提供了一套验证的工具,这样可以使得请求的验证统一而且很方便
流程
- MockMvcBuilder实例化MockMvc
- mockMvc调用perform,执行一个RequestBuilder请求,调用controller的业务处理逻辑
- perform返回ResultActions,返回操作结果,通过ResultActions,提供了统一的验证方式
- 使用StatusResultMatchers对请求结果进行验证
- 使用ContentResultMatchers对请求返回的内容进行验证
代码
这里是完整的测试案例:包含Junit5以及Mockmvc
package com.uncle.junittest.bean; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** * @author * @date 2022-01-27 上午10:30 **/ @Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable { /** * 序列号 */ private static final long serialVersionUID = -4449140937612137858L; /** * 用户id */ private String id; /** * 昵称 */ private String fullName; /** * 用户名 */ private String userName; /** * 密码 */ private String password; /** * 年龄 */ private Integer age; /** * 性别 */ private String sex; }
package com.uncle.junittest.mapper; import com.uncle.junittest.bean.User; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Select; import org.springframework.stereotype.Repository; /** * @author * @date 2022-01-27 下午3:30 **/ @Repository public interface UserMapper { /** * 增加用户的方法 * * @param user 用户信息 * @return 增加成功返回:1;否则返回:0 */ @Insert("insert into user values (#{id},#{fullName},#{userName},#{password},#{age},#{sex})") int saveUser(User user); /** * 根据id查询用户信息 * * @param id 用户id * @return 用户信息 */ @Select("select * from user where id = #{id}") User getUser(String id); }
package com.uncle.junittest.service; import com.uncle.junittest.bean.User; import org.apache.ibatis.annotations.Select; /** * @author * @date 2022-01-27 下午3:29 **/ public interface UserService { /** * 增加用户的方法 * @param user 用户信息 * @return 增加成功返回:1;否则返回:0 */ int saveUser(User user); /** * 根据id查询用户信息 * @param id 用户id * @return 用户信息 */ User getUser(String id); }
package com.uncle.junittest.service.impl; import com.uncle.junittest.bean.User; import com.uncle.junittest.mapper.UserMapper; import com.uncle.junittest.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author * @date 2022-01-27 下午3:29 **/ @Service public class UserServiceImpl implements UserService { /** * UserMapper对象 */ @Autowired private UserMapper userMapper; /** * 增加用户的方法 * @param user 用户信息 * @return 增加成功返回:1;否则返回:0 */ @Override public int saveUser(User user) { return userMapper.saveUser(user); } /** * 根据id查询用户信息 * @param id 用户id * @return 用户信息 */ @Override public User getUser(String id) { return userMapper.getUser(id); } }
package com.uncle.junittest.controller; import com.uncle.junittest.bean.User; import com.uncle.junittest.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * @author * @date 2022-01-27 下午3:29 **/ @RequestMapping("/test") @RestController public class UserController { /** * UserService对象 */ @Autowired private UserService userService; /** * 新增用户 * @param user User * @return String */ @PostMapping("/saveUser") public String saveUser(@RequestBody User user) { int i = userService.saveUser(user); return i == 1 ? user.toString() : "新增用户失败"; } /** * 获取用户信息 * * @return String */ @GetMapping("/getUser/{id}") public String getUser(@PathVariable(value = "id") String id) { User user = userService.getUser(id); return user.toString(); } }
package com.uncle.junittest; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.uncle.junittest.mapper") public class JunitTestApplication { public static void main(String[] args) { SpringApplication.run(JunitTestApplication.class, args); } }
spring: datasource: username: root password: url: jdbc:mysql://@rm-2ze44u6e2z7t924t0xo.mysql.rds.aliyuncs.com:3306/ server: port: 80 mybatis: configuration: map-underscore-to-camel-case: true
package com.uncle.junittest.controller; import com.alibaba.fastjson.JSON; import com.uncle.junittest.bean.User; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.*; 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.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.LinkedMultiValueMap; import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; /** * 用户测试类 * * @AutoConfigureMockMvc:该注解将会自动配置mockMvc的单元测试 */ @DisplayName(value = "用户测试类") @Slf4j @AutoConfigureMockMvc @SpringBootTest class UserControllerTest { /** * 每个单元测试之前都要执行 */ @BeforeEach void testBeforeEach() { log.info("测试要开始了!"); } /** * 每个单元测试之后都要执行 */ @AfterEach void testAfterEach() { log.info("测试结束了!"); } /** * 在所有测试之前执行 */ @BeforeAll static void testBeforeAll() { log.info("要测试所有测试了!"); } /** * 在所有测试之后执行 */ @AfterAll static void testAfterAll() { log.info("所有测试测试完了!"); } /** * mockmvc */ @Autowired MockMvc mockMvc; /** * 新增用户接口测试 * * @Disabled:该注解标识着不需要启动测试 * @Transactional:事物回滚 */ @Disabled @DisplayName("新增用户接口测试") @Transactional @Test void saveUser() throws Exception { // 请求地址 String urlTemplate = "/test/saveUser"; /* 请求头集合 */ HttpHeaders headers = new HttpHeaders(); headers.add("header1", "header1"); headers.add("header2", "header2"); /* 参数体 */ String uuid = UUID.randomUUID().toString(); User user = new User(); user.setId("4"); user.setFullName("copico"); user.setUserName("kebike"); user.setPassword("123456"); user.setAge(19); user.setSex("女"); // 转换成json对象 String jsonString = JSON.toJSONString(user); // 请求 RequestBuilder request = MockMvcRequestBuilders // post请求 .post(urlTemplate) // 数据类型 .contentType(MediaType.APPLICATION_JSON) // 请求头 .headers(headers) // 请求体 .content(jsonString); MvcResult mvcResult = mockMvc.perform(request) // 打印日志 .andDo(print()) // 获取返回值 .andReturn(); // 从返回值获取状态码 int status = mvcResult.getResponse().getStatus(); //从返回值获取响应的内容 String contentAsString = mvcResult.getResponse().getContentAsString(); // 断言 assertAll("heading", () -> assertEquals(200, status, "保存失败"), () -> assertEquals(user.toString(), contentAsString, "数据保存失败")); } /** * 获取用户接口测试 */ @DisplayName("获取用户接口测试") @Test void getUser() throws Exception { // 预期结果 User user = new User("1", "小软", "neusoft", "123456",null,null); // 请求地址 String urlTemplate = "/test/getUser/1"; /* 请求头集合 */ HttpHeaders headers = new HttpHeaders(); headers.add("headers1", "headers1"); headers.add("headers2", "headers2"); LinkedMultiValueMap<String,String> linkedMultiValueMap = new LinkedMultiValueMap(); linkedMultiValueMap.add("params1", "params1"); linkedMultiValueMap.add("params2", "params2"); // 请求 RequestBuilder request = MockMvcRequestBuilders // post请求 .get(urlTemplate) // 数据类型 .contentType(MediaType.APPLICATION_JSON) // 请求头 .header("header1","header1") .headers(headers) // 请求参数 .param("param1","param2") .params(linkedMultiValueMap); MvcResult mvcResult = mockMvc.perform(request) // 打印日志 .andDo(print()) // 获取返回值 .andReturn(); // 从返回值获取状态码 int status = mvcResult.getResponse().getStatus(); //从返回值获取响应的内容 String contentAsString = mvcResult.getResponse().getContentAsString(); // 断言 assertAll("heading", () -> assertEquals(200, status, "用户信息获取失败"), () -> assertEquals(user.toString(), contentAsString, "数据不一致,获取失败")); } /** * * 规定方法的超时时间 * 超出时间测试异常 * @throws InterruptedException */ @DisplayName(value = "超出时间测试") @Timeout(value = 500,unit = TimeUnit.MILLISECONDS) @Test void testTimeout() throws InterruptedException { Thread.sleep(1000); } /** * 重复测试n次 */ @RepeatedTest(value = 2) void repeatedTest() { assertArrayEquals(new int[]{1, 2}, new int[]{1, 2}); } }
<?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.3.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.uncle</groupId> <artifactId>junit-test</artifactId> <version>0.0.1-SNAPSHOT</version> <name>junit-test</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.75</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <!-- <version>5.1.47</version>--> </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> </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> </build> </project>