springboot 单元测试实践3

764 阅读3分钟

本文重点来写点代码,对spring boot中http接口的返回进行单测。我们在springboot单元测试实践1中,已经写了如何mockcontroller的请求,测试controller中的逻辑,并简单针对返回体response进行了简单的测试。

在实际的生产环境中,spring boot对外提供的http接口的返回,根据具体的业务场景可能是个很复杂的数据结构。目前,大多数前后端分离的项目,返回的内容都是一整段的json格式的数据。

所以,我们对response做测试,大部分就是在针对response中返回的json做校验,json中有可能返回的是array,map等嵌套的复杂结构。

新建http请求接口

在前文的DemoController中,新建几个http的请求接口

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
@Slf4j
public class DemoController {

  @GetMapping("/log")
  public String log() {
    log.info("test print log");
    return "log";
  }

  @GetMapping("/list")
  public List<String> stringList() {

    List<String> list = new ArrayList<>();
    list.add("one");
    list.add("two");
    list.add("three");
    return list;
  }

  @GetMapping("/map")
  public Object mapList() {
    List<String> list = new ArrayList<>();
    list.add("one");
    list.add("two");
    list.add("three");
    return Map.of("list", list);
  }

  @GetMapping("/result")
  public Object resultList() {
    Result result1 = new Result();
    result1.setKey("key1");
    Result result2 = new Result();
    result2.setKey("key2");
    return List.of(result1, result2);
  }

  @Getter
  @Setter
  private class Result {
    private String key;
    private String value;
  }
}

内容中新增了/api/list 、 /api/map、 /api/result 三个接口

/api/list 的单测

/api/list 主要是用来模拟直接返回一个list的列表,也就是说实际的response是下面这个样子的

    ["one", "two", "three"]

针对 /api/list,写了下面的测试case

@Test
void stringList() throws Exception {
  this.mockMvc
      .perform(MockMvcRequestBuilders.get("/api/list"))
      .andDo(print())
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.iterableWithSize(3)));
}

.andDo(print()) 实际上就是用来在控制台打印具体的response内容,这样方便比对response的内容进行针对性的测试

jsonPath方法是用来将response按照json的格式来进行解析,"$"符号代表着json的根对象,因为我们返回的是个list,所以根对象实际指的就是当前返回的array数组,这里我们期望返回的array列表的大小是3

/api/map 的单测

/api/map 主要是用来模拟直接返回一个map,也就是说实际的response是下面的这个样子的, 这里我们直接使用andDo(print())的控制台的打印来检查返回内容

image.png

针对 /api/map, 写了下面的测试case

@Test
void mapList() throws Exception {
  this.mockMvc
      .perform(MockMvcRequestBuilders.get("/api/map"))
      .andDo(print())
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.jsonPath("$").isMap())
      .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.hasKey("list")))
      .andExpect(MockMvcResultMatchers.jsonPath("$.list", Matchers.iterableWithSize(3)))
      .andExpect(MockMvcResultMatchers.jsonPath("$.list[0]", Matchers.is("one")));
}

首先,我们测试jsonPath拿到的根对象的内容是不是一个map,再来检查map返回的内容中是不是有一个list的key,再来检查list返回的结果的大小是不是3个,最后来获取list中第一个对象的内容

/api/result接口的单测

/api/result 主要是用来模拟直接返回一个对象的列表,我们直接使用andDo(print())的控制台的打印来检查返回内容

image.png

针对 /api/result,写了下面的测试case

@Test
void resultList() throws Exception {
  this.mockMvc
      .perform(MockMvcRequestBuilders.get("/api/result"))
      .andDo(print())
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.jsonPath("$").isArray())
      .andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.iterableWithSize(2)))
      .andExpect(
          MockMvcResultMatchers.jsonPath(
              "$", Matchers.everyItem(Matchers.allOf(Matchers.hasKey("key")))))
      .andExpect(MockMvcResultMatchers.jsonPath("$[0].key", Matchers.is("key1")));
}

我们先测试jsonPath拿到的根对象是不是一个array数组,再检查列表中的大小是不是2,再检查列表中所有的对象,都包含key这个字段,最后检查列表中的第一个对象的key字段对应的值为key1

好了,针对json的测试目前也就这么多,更复杂的测试,可以多去看看MockMvcResultMatchers中提供的api和Matchers中提供的校验方法。

就到这里了,如果有复杂的场景,也可以在评论里写出来,一起来玩转业务场景的各种单测case