(十)从零搭建后端框架——MockMvc单元测试

2,381 阅读3分钟

前言

在功能开发完成后,虽然有专门的测试人员,但开发人员自身也要进行单元测试。

一般公司对BUG率和单元测试覆盖率都会有一定的要求,所以做好单元测试还是很有必要的。

后端提供的都是接口,本文使用MockMvc模拟接口进行测试。

具体实现

Maven依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

为了防止测试对真实数据库数据产生影响,这里使用H2数据库,并进行参数配置。

参数配置

spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:test;MODE=MYSQL;
    schema: classpath:sqls/sys.sql
  main:
    banner-mode: off
mybatis:
  mapper-locations: classpath*:sql-mappers/**/*.xml
  • spring.datasource.schema 指定建表语句文件
  • mybatis.mapper-locations 指定Mapper文件

示例

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {

    @Autowired
    private MockMvc mvc;

    private static Gson gson = new GsonBuilder().serializeNulls().create();

    @Test
    @SqlGroup({
            @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, value = "classpath:h2/user/init-data.sql"),
            @Sql(executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, value = "classpath:h2/user/clean-data.sql")
    })
    void getUser() throws Exception {
        // 模拟GET请求
        mvc.perform(get("/user/1")
                // 请求参数类型
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                // 接收参数类型
                .accept(MediaType.APPLICATION_JSON_UTF8))
                // 结果验证
                .andExpect(status().isOk())
                .andExpect(jsonPath("code").value(200))
                .andExpect(jsonPath("data").exists())
                .andExpect(jsonPath("$['data']['account']").value("zhuqc1"))
                .andExpect(jsonPath("$['data']['password']").value("password"))
                .andExpect(jsonPath("$['data']['nickname']").value("zhuqc1"))
                .andExpect(jsonPath("$['data']['email']").value("zqc@zqc.com"))
                .andExpect(jsonPath("$['data']['phone']").value("13345678901"))
                // 结果处理器
                .andDo(MockMvcResultHandlers.print());
    }

    @Test
    @SqlGroup({
            @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, value = "classpath:h2/user/init-data.sql"),
            @Sql(executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, value = "classpath:h2/user/clean-data.sql")
    })
    void getUser2() throws Exception {
        // 模拟GET请求,并返回结果
        MvcResult result = mvc.perform(get("/user/1")
                .accept(MediaType.APPLICATION_JSON_UTF8))
                .andReturn();
        MockHttpServletResponse response = result.getResponse();
        JsonObject apiResult = JsonParser.parseString(response.getContentAsString()).getAsJsonObject();
        JsonObject data = apiResult.getAsJsonObject("data").getAsJsonObject();

        // 断言验证数据
        Assertions.assertEquals(response.getStatus(), HttpStatus.OK.value());
        Assertions.assertEquals(data.get("account").getAsString(), "zhuqc1");
        Assertions.assertEquals(data.get("password").getAsString(), "password");
        Assertions.assertEquals(data.get("nickname").getAsString(), "zhuqc1");
        Assertions.assertEquals(data.get("email").getAsString(), "zqc@zqc.com");
        Assertions.assertEquals(data.get("phone").getAsString(), "13345678901");
    }

    @Test
    @SqlGroup({
            @Sql(executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, value = "classpath:h2/user/clean-data.sql")
    })
    void addUser() throws Exception {
        User user = new User();
        user.setAccount("zhuqc1");
        user.setPassword("password");
        user.setNickname("zhuqc1");
        user.setEmail("zqc@zqc.com");
        user.setPhone("13345678901");

        // 模拟POST请求
        mvc.perform(post("/user")
                // 请求参数类型
                .contentType(MediaType.APPLICATION_JSON_UTF8)
                // 接收参数类型
                .accept(MediaType.APPLICATION_JSON_UTF8)
                // 请求参数
                .content(gson.toJson(user)))
                // 结果验证
                .andExpect(status().isOk())
                .andExpect(jsonPath("code").value(200))
                .andExpect(jsonPath("data").value(1));
    }
}
  • @SpringBootTest 指定测试类在SpringBoot环境下运行

  • @AutoConfigureMockMvc 用于自动配置MockMvc,配置后MockMvc类可以直接注入

  • @SqlGroup 指定测试方法执行前后的SQL语句

    比如,可以在测试方法执行前,初始化数据;在测试方法执行后,清除数据。

MockMvc

MockMvc是接口测试的主入口,核心方法perform(RequestBuilder), 会自动执行SpringMVC的流程并映射到相应的控制器执行处理,方法返回值是ResultActions。

ResultActions

  • andExpect 添加ResultMatcher验证规则,验证执行结果是否正确。
  • andDo 添加ResultHandler结果处理器,比如打印结果到控制台。
  • andReturn 返回MvcResult执行结果,可以对结果进行自定义验证。

MockMvcResultMatchers

用于验证执行结果是否正确,详见测试方法getUser()。

MockMvcResultHandlers

结果处理器,表示要对结果做点什么事情。详见测试方法getUser()。

比如使用MockMvcResultHandlers.print()打印响应结果信息到控制台。如下:

响应结果

MvcResult

单元测试执行结果,可以对结果进行自定义验证,详见测试方法getUser2()。

源码

github.com/zhuqianchan…

往期回顾