第八节:初始化单元测试 -- controller -- get

前面我们在开发servicerespository时,都引用了单元测试。在开发controller时,采用的是postMan的方式。 使用postMan的优点的操作简单,数据返回直观。缺点当然也是有的,比如上节中,我们遇到的问题。那么,我们在开发controler时,是否也可以引用单元测试呢?

答案是肯定的。Spring的官方入门教程,对这个问题进行了讲解: https://spring.io/guides/gs/testing-web/

在官方教程的Building REST services with Spring中,也专门对控制器测试进行了讲解:https://spring.io/guides/tutorials/bookmarks/

此外,官方文档中,测试又单独的做为一个章节来呈现给我们: https://docs.spring.io/spring-boot/docs/4.3.7.RELEASE/reference/html/boot-features-testing.html

基本代码

我们结合idea生成基本的测试代码如下:

package com.mengyunzhi.controller;

import org.springframework.boot.test.autoconfigure.web.servlet.*;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by panjie on 17/4/14.
 */
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc       // 自动配置 模块MVC
public class KlassControllerTest {

    @Autowired
    private MockMvc mockMvc;


    @Test
    public void save() throws Exception {
      
    }

    @Test
    // 由于get()与http请求的get命名相同,在这里重新命名以防止冲突。
    public void getTest() throws Exception {

    }

    @Test
    public void update() throws Exception {

    }

    @Test
    public void delete() throws Exception {

    }
}

getTest

添加注释:

    @Test
    // 由于get()与http请求的get命名相同,在这里重新命名以防止冲突。
    public void getTest() throws Exception {
        // 直接添加一个实体
        
        // 发起http请求来查询这个实体

        // 断言这个实体查询成功
    }

添加实体:

    // 由于get()与http请求的get命名相同,在这里重新命名以防止冲突。
    public void getTest() throws Exception {
        // 直接添加一个实体
        Klass klass = klassService.getNewKlass();
        String klassId = klass.getId().toString();

        // 发起http请求来查询这个实体

        // 断言这个实体查询成功
    }

测试:

Hibernate: insert into teacher (address, email, name, sex) values (?, ?, ?, ?)
Hibernate: insert into klass (name, teacher_id) values (?, ?)

查询实体:

    public void getTest() throws Exception {
        // 直接添加一个实体
        Klass klass = klassService.getNewKlass();
        String klassId = klass.getId().toString();

        // 发起http请求来查询这个实体
        // 1.发请请求
        // 2. 打印请求结果

        // 断言这个实体查询成功
    }

发起请求:

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import org.springframework.test.web.servlet.ResultActions;
        // 1.发请请求
        ResultActions resultActions = this.mockMvc.perform(get("/klass/" + klassId));

解读:ResultActions返回类型,performthis.mockMvc中的一个方法,接收的参数是get()方法的返回值,get方法的接收参数是绝对路径的字符串。

打印请求结果:

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
        // 2. 打印请求结果
        resultActions.andDo(print());

代码解读:andDoresultActions对象中的方法,这个方法接收的类型是print()函数的返回值。这两项共同起到了讲请求结果打印至控制台的作用。

测试:

MockHttpServletRequest:         // 模拟HTTP请求信息
      HTTP Method = GET         // 请求方法
      Request URI = /klass/1    // 请求地址
       Parameters = {}          // 请求参数
          Headers = {}          // 请求头信息
// 处理信息
Handler:    
            // 请求类型(对应的类名) 请求方法(对应的方法名) 请求方法中的参数类型
             Type = com.mengyunzhi.controller.KlassController
           Method = public com.mengyunzhi.repository.Klass  com.mengyunzhi.controller.KlassController.get(java.lang.Long)

Async:      // 异步信息
    Async started = false       // 是否异步请求
     Async result = null        // 异步结果 

Resolved Exception:             // 异常信息
             Type = null

ModelAndView:                   // ModelAndView 在我们前台后分离中,这个暂时用不到。
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:        // 模拟请求响应
           Status = 200         // 状态码 200 
    Error message = null        // 错误信息
          Headers = {Content-Type=[application/json;charset=UTF-8]}    // Header信息 
     Content type = application/json;charset=UTF-8                      // Content type
             Body = {"id":2,"teacher":{"id":2,"name":"示例教师","email":"zhangsan@yunzhiclub.com","address":"scse of hebut","sex":true},"name":"示例班级"} // Body,返回的主要内容
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

在控制台中,详细的打印请求及该请求对应的响应信息。是的,这部分内容我们再熟悉不过了。有了伟大MockMVC,我们再也不必为交流测试而发愁了。离优秀的团队开发,我们又近了一步。

打印请求结果


import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.hamcrest.Matchers.is;
    public void getTest() throws Exception {
        // 直接添加一个实体
        Klass klass = klassService.getNewKlass();
        String klassId = klass.getId().toString();

        // 发起http请求来查询这个实体
        // 1.发请请求
        ResultActions resultActions = this.mockMvc.perform(get("/klass/" + klassId));
        // 2. 打印请求结果
        resultActions.andDo(print());

        // 断言这个实体查询成功
        resultActions.andExpect(jsonPath("$.id", is(klass.getId().intValue())));
    }

解读:andExpect – 建立一个断言, jsonPath() – 将返回的body数据转化为json并进行断言判断, $.id$代表返回的json对象,is() – 断言值。 上述代码整体表示:建立一个断言:期待返回的结果成功转化为json格式的数据,转化为json格式数据后,其中的id属性的值为klassid值。

测试:

/SpringMVC/assets/image/chapter3/34.png

代码重构

上述代码采用链式调用后,如下:

    @Test
    // 由于get()与http请求的get命名相同,在这里重新命名以防止冲突。
    public void getTest() throws Exception {
        // 直接添加一个实体
        Klass klass = klassService.getNewKlass();

        // 发起http请求来查询这个实体
        this.mockMvc.perform(get("/klass/" + klass.getId().toString()))
            .andDo(print())
            .andExpect(jsonPath("$.id", is(klass.getId().intValue())));
    }

在教程的编写过程中,IDEA并没能自动为我们添加好所有的依赖包。故在此给出完事的代码供参考:

package com.mengyunzhi.controller;

import com.mengyunzhi.repository.Klass;
import com.mengyunzhi.service.KlassService;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;


import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

import static org.hamcrest.Matchers.is;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.ResultActions;


/**
 * Created by panjie on 17/4/14.
 */
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc       // 自动配置 模似MVC
public class KlassControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private KlassService klassService;

    @Test
    public void save() throws Exception {

    }

    @Test
    // 由于get()与http请求的get命名相同,在这里重新命名以防止冲突。
    public void getTest() throws Exception {
        // 直接添加一个实体
        Klass klass = klassService.getNewKlass();

        // 发起http请求来查询这个实体
        this.mockMvc.perform(get("/klass/" + klass.getId().toString()))
                .andDo(print())
                .andExpect(jsonPath("$.id", is(klass.getId().intValue())));
    }

    @Test
    public void update() throws Exception {

    }

    @Test
    public void delete() throws Exception {

    }
}

总结:

本节中,我们使用了mockMvc模拟进行http请求,并使用print()方法,将请求结果打印到了控制台中。最后,使用jsonPath()对响应结果进行了json转换,转换后,使用is()成功的进行了断言。

参考

https://spring.io/guides/gs/testing-web/ https://spring.io/guides/tutorials/bookmarks/ https://docs.spring.io/spring-boot/docs/4.3.7.RELEASE/reference/html/boot-features-testing.html https://docs.spring.io/spring-boot/docs/4.3.7.RELEASE/reference/html/test-auto-configuration.html