在SpringBoot项目中使用MockMvc进行接口测试

在SpringBoot项目中使用MockMvc进行接口测试

现在流行在项目中使用 swagger对接口进行测试,这确实很方便、直观。

但是MockMvc作为spring-test包中指定的测试框架,在没有使用swagger的项目中,使用其进行测试是很好的选择。

本文简单介绍在springboot项目中使用 MockitoMockMvc对控制器进行测试。

1 了解Mockito #

简单来说, Mockito是一个模拟创建对象的框架,利用它提供的API,可以简化单元测试工作。Mockito的API易读性是很好的,并且错误信息也很简明。spring-boot-starter-test模块中引入了mockito依赖,如果你使用springboot,那么就可以直接使用Mockito进行单元测试。

我们从Mockito 官方API文档的的引例开始,看看Mockito是如何工作的。

1.1 mock一个对象 #

 1
 2 // 学会使用静态导入,代码会更简洁
 3 import static org.mockito.Mockito.*;
 4
 5 // mock List接口对象
 6 List mockedList = mock(List.class);
 7
 8 // 使用Mock的List对象
 9 mockedList.add("one");
10 mockedList.clear();
11
12 // 校验某个行为是否发生过1次
13 verify(mockedList).add("one");
14 verify(mockedList).clear();

一旦mock对象被创建,mock会记住对其的所有操作,之后,你便可以选择性的校验这些操作。

1.2 绑定方法参数和返回值 #

 1 // 也可以mock实体类对象
 2 LinkedList mockedList = mock(LinkedList.class);
 3
 4 // 为指定参数的操作绑定返回值(stubbing)
 5 when(mockedList.get(0)).thenReturn("first");
 6 when(mockedList.get(1)).thenThrow(new RuntimeException());
 7
 8 // 打印 first
 9 System.out.println(mockedList.get(0));
10
11 // 抛出 RunTimeException
12 System.out.println(mockedList.get(1));
13
14 // 打印null,因为get(999)的返回值没有指定
15 System.out.println(mockedList.get(999));
16
17 // 尽管也可以对绑定操作进行校验,不过这通常是非必要的
18 // 如果你关注get(0)的返回值,那么你应该在代码里进行测试
19 // 如果get(0)的返回值无关紧要,那么就没有必要进行绑定
20 verify(mockedList).get(0);

一般来说,对于任意有返回值的方法,mockito都会返回null、原始类型/原始类型的包装类、或者一个空的集合。

返回值的绑定操作可以被覆盖

 1// 返回值的绑定可以连续设置
 2// 最后一次绑定就是实际调用的返回值
 3// 例如,mock.someMethod("some arg")将返回“foo”
 4when(mock.someMethod("some arg"))
 5    .thenThrow(new RuntimeException())
 6    .thenReturn("foo");
 7
 8// 连续绑定的简单形式:
 9when(mock.someMethod("some arg"))
10    .thenReturn("one", "two");
11// 等价于:
12when(mock.someMethod("some arg"))
13    .thenReturn("one")
14    .thenReturn("two");
15
16// 抛出异常的简单形式:
17when(mock.someMethod("some arg"))
18    .thenThrow(new RuntimeException(), new NullPointerException());

一旦方法的返回值被绑定,那么其将一直返回绑定的值,无论其被调用多少次。

1.3 参数匹配器 #

上面形式的返回值绑定在测试时似乎很好用,我们构建参数,设置预期的返回结果,再进行校验即可。但仔细想想,或许少了点什么?对,少了参数的模糊匹配,比如我想绑定get(int)方法的返回值,无论其参数是多少。mockito自然能够为我们做到这些:

 1 // 使用mockito内建的anyInt()来进行匹配
 2 when(mockedList.get(anyInt())).thenReturn("element");
 3
 4 //stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
 5 // 使用自定义matcher进行绑定
 6 when(mockedList.contains(argThat(isValid()))).thenReturn(true);
 7
 8 //following prints "element"
 9 // 打印999
10 System.out.println(mockedList.get(999));
11
12 // 也可以校验方法被调用了一次
13 verify(mockedList).get(anyInt());
14
15 // mockito同样支持java8的lambda表达式进行参数匹配
16 verify(mockedList).add(argThat(someString -> someString.length() > 5));

参数匹配可以方便地进行动态返回值绑定校验。

想了解更多关于 argument matcher和hamcrest matcher的内容,可参考:

除了使用matcher之外,mockito还支持使用类型(class)匹配,这种参数匹配方式在进行MVC测试时,对json参数进行序列化和反序列化时尤其有用:

1when(spittleService.pageQuerySpittlesByTimeLine(any(SpittleDTO.class))).thenReturn(page);
2
3 ResultActions resultActions = mockMvc
4                .perform(MockMvcRequestBuilders.post("/spittle/range/spittles")
5                        .contentType(MediaType.APPLICATION_JSON)
6                        .content(jsonString)
7                );

就像上面那样,在使用RequestBody传参时,若使用JSON,需要对json字符串进行反序列化。这种情形,在进行参数绑定时,自然不能使用

1when(spittleService.pageQuerySpittlesByTimeLine(spittleDTO).thenReturn(page);

这样的形式,因为控制器接收到的必然不是这个指定的spittleDTO对象。使用类类型参数,mockito进行参数匹配时,使用equals方法比较的对象的相等性,因此可以获取绑定的返回值。

Sometimes its just better to refactor the code to allow equals() matching or even implement equals() method to help out with testing.

1.4 检验方法被调用的次数 #

前文我们提到,verify()方法可以校验指定方法被调用过一次。

mokito提供了更加灵活的校验API,可以用来检验指定方法被调用的次数:

 1//using mock
 2mockedList.add("once");
 3
 4mockedList.add("twice");
 5mockedList.add("twice");
 6
 7mockedList.add("three times");
 8mockedList.add("three times");
 9mockedList.add("three times");
10
11// 当verify方法不指定次数时,默认检验方法调用1次,以下2个调用是等价的
12verify(mockedList).add("once");
13verify(mockedList, times(1)).add("once");
14
15// add("twice)被调用了2次
16verify(mockedList, times(2)).add("twice");
17// add("three times")被调用了3次
18verify(mockedList, times(3)).add("three times");
19
20// add("never happened")方法没有被调用
21// 等价于 times(0)
22verify(mockedList, never()).add("never happened");
23
24// 使用atLeast()/atMost()可以校验参数至少/至多被调用几次
25verify(mockedList, atMostOnce()).add("once");
26verify(mockedList, atLeastOnce()).add("three times");
27verify(mockedList, atLeast(2)).add("three times");
28verify(mockedList, atMost(5)).add("three times");

times(1)是默认,因此verify(mockedList, times(1)).add("once")这样的形式是不必要的。

除了上面介绍的之外,moikito还有很多使用的测试方法,具体可以参考API文档:

2 使用MockMvc测试控制器 #

介绍了mockito的基本用法,可以开始用它测试控制器了。

spring web项目的测试使用的是Spring MVC测试框架(Spring MVC Test framework(MockMvc)),其使用方式和Mockito很像,实际上MockMvc借用了Mockito的API,因此,熟悉Mockito的使用对使用MockMVC测试web服务大有裨益。

2.1 熟悉这几个静态导入 #

  • MockMvcBuilders.*
  • MockMvcRequestBuilders.*
  • MockMvcResultMatchers.*
  • MockMvcResultHandlers.*

和Mockito一样,熟悉并使用静态导入会让代码看起来更简洁。不过,对刚使用MockMVC进行测试的新手来说,使用静态导入可能会陷入一个麻烦:方法这么多,我怎么记得这个方法该使用哪个静态导入,容易陷入混乱。

不过,记住他们的惯用法就行了:

 1// MockMvcBuilders.* 用于构建MockMvc应用
 2MockMvc mockMvc = MockMvcBuilders.stansaloneSetup(controller).build();
 3
 4// MockMvcRequestBuilders.*用于构建请求
 5ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get(url));
 6ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.post(url));
 7
 8// MockMvcResultMatchers.*用于请求结果匹配
 9resultActions.andExpect(MockMvcResultMatchers.status().isOK())
10
11// MockMvcResultHandlers.* 嘛,用得少,其提供一个print()方法,可以打印请求信息
12resultActions.andDo(MockMvcResultHandlers.print());

2.2 测试示例 #

在进行单元测试时,通常习惯将通用模版进行抽象,本示例中也是如此,我们建立一个抽象测试类,用于准备数据、提供通用方法等:

 1@SpringBootTest
 2@TestPropertySource("classpath:application-test.properties")
 3public class BaseMockInit {
 4
 5//    @Autowired
 6//    protected ObjectMapper objectMapper;
 7    protected ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
 8
 9
10    protected @Mock
11    ISpitterService spitterService;
12    protected @Mock
13    ISpittleService spittleService;
14    protected SpitterController spitterController;
15    protected SpittleController spittleController;
16
17    @BeforeEach
18    void initMock() {
19        MockitoAnnotations.initMocks(this);
20        spitterController = new SpitterController();
21        spitterController.setSpitterService(spitterService);
22        spittleController = new SpittleController();
23        spittleController.setSpittleService(spittleService);
24    }
25}

你可能注意到上面的示例中使用的@Mock注解和MockitoAnnotations.initMocks(this);方法,实际作用就是mock web测试中所需要使用到的服务层service,因为测试web模块不涉及到数据服务层的业务,因此借助Mockito即可轻松创建测试所需要的实例。

2.2.1 简单路径参数GET请求测试 #

 1 @Test
 2public void getSpitterById() throws Exception {
 3    SpitterVO source = new SpitterVO(1, "alan", "walker", "aw", "xxx");
 4    Spitter spitter = new Spitter();
 5    BeanUtils.copyBeanProp(spitter, source);
 6
 7    when(spitterService.getById(1)).thenReturn(spitter);
 8    spitterController.setSpitterService(spitterService);
 9    MockMvc mockMvc = standaloneSetup(spitterController).build();
10    // perform get request with path variables
11    ResultActions resultActions = mockMvc.perform(get("/spitter/1"));
12    log.info(resultActions.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8));
13    resultActions.andExpect(status().isOk())
14            .andExpect(content().contentType(MediaType.APPLICATION_JSON))
15            .andExpect(jsonPath("$.data")
16                    .value(objectMapper.convertValue(spitter, HashMap.class)));
17
18    verify(spitterService).getById(1);
19}

观察上面的测试用例,我们首先使用Mockito对数据层的mock对象进行了参数和返回值绑定,这在前文已经提及:

1when(spitterService.getById(1)).thenReturn(spitter);

随即使用MockMvc发起get请求,发起请求的方式有多种:

1ResultActions resultActions = mockMvc.perform(get("/spitter/1"));
2// 等价于
3ResultActions resultActions = mockMvc.perform(get("/spitter/{id}", 1));

当请求进入控制器时,根据控制器的业务逻辑,调用spitterService.getById(1)方法,该方法返回之前绑定的返回值,进行封装之后,返回web请求的结果。

上述请求返回一个ResultActions结果,web请求的结果被封装在内,我们可以对这个结果进行校验:

1resultActions.andExpect(status().isOk())
2            .andExpect(content().contentType(MediaType.APPLICATION_JSON))
3            .andExpect(jsonPath("$.data")
4                    .value(objectMapper.convertValue(spitter, HashMap.class)));

注意到,jsonPath("$.data"),这意味着请求返回的json字串中包含一个data键,.value()操作暗示其对应的内容就是spitterService.getById(1)的返回对象。所以这个请求返回的json应该像这样:

 1{
 2    "code": 200,
 3    "message": "ok",
 4    "data": {
 5        "id": 1,
 6        "usename": "aw",
 7        "firstname": "alan",
 8        "lastname": "walker",
 9        "password": "xxx"
10    }
11}
1  .andExpect(jsonPath("$.data").value(objectMapper.convertValue(spitter, HashMap.class)));

意义是比较通过jsonPath("$.data")解析到的对象和objectMapper.convertValue(spitter, HashMap.class))获取到的对象的相等性。

实际上,通过jsonPath("$.data")获取到的内容是一个LinkedHashMap,而.value()的相等性比较的是map中对应键的值的相等性,单单从这个示例来讲,这个比较是可行的1

最后,我们使用verify方法对mock对象的方法调用进行了测试:

1 verify(spitterService).getById(1);

不过,由于我们已经校验了web接口的返回值,那么mock对象的方法一定被调用了,所以一般我们无需这么做。

2.2.2 拼接参数的GET方法测试 #

除了路径参数,使用最多的就是形如?para1=xxx&para2=xxx这样的请求参数,MockMvc同样对这样的web服务提供测试支持

 1@Test
 2public void getUserSpittlesPageTest() throws Exception {
 3    // ... 省略准备数据
 4
 5    // 此处必须使用类类型作为参数
 6    when(spittleService.pageQuerySpittleBySpitterId(any(SpittleDTO.class))).thenReturn(page);
 7
 8    // perform get with request params transferred by pojo
 9    ResultActions resultActions = mockMvc
10        .perform(get("/spittle/user/spittles?spitterId={spitterId}", 4))
11        // get element from json
12        // see https://github.com/json-path/JsonPath
13        .andExpect(jsonPath("$.data.currentPage")
14            .value(pageDomain.getCurrentPage()))
15        .andExpect(jsonPath("$.data.pageSize")
16            .value(pageDomain.getPageSize()))
17        .andExpect(jsonPath("$.data.pages")
18            .value(pageDomain.getPages()))
19        .andExpect(jsonPath("$.data.total")
20            .value(pageDomain.getTotal()))
21        .andDo(print());
22        /* 报错原因 :long和integer的问题*/
23//            .andExpect(jsonPath("$.data.records[0]")
24//                .value(objectMapper.convertValue(sample, HashMap.class)));
25
26    String jsonResult = resultActions.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
27    log.info(jsonResult);
28    // verify is not necessary here
29    verify(spittleService).pageQuerySpittleBySpitterId(any(SpittleDTO.class));
30
31    assertEquals((int) jsonPathParser(jsonResult).read("$.data.records.length()"), 1);
32    SpittleVO rvo = jsonPathParser(jsonResult).read("$.data.records[0]", SpittleVO.class);
33    assertEquals(sample, rvo);
34}

这个测试和上一个测试有一些区别,首先第一个区别就是mock对象的参数与返回值绑定方式变了:

1 when(spittleService.pageQuerySpittleBySpitterId(any(SpittleDTO.class))).thenReturn(page);

多数情况下,我们不会直接在控制器中使用具体的参数,而是使用Java Bean作为控制器的参数。这个时候,Spring MVC的MappingJasksonHttpMessageConverter将会发挥作用2,将请求中的中的参数转换为对应的Java Bean实例。

这样一来,我们便不能指定某一个实例作为mock对象的参数了,只能使用any(class)这样的形式进行模糊匹配。

其次,关于使用地址栏参数的参数传递,除了使用上述的方式(最简单)之外,还有其他的方式:

1  ResultActions resultActions = mockMvc.perform(get("/spittle/user/spittles?spitterId={spitterId}", 4))
2  // 等价于
3  ResultActions resultActions = mockMvc.perform(get("/spittle/user")).param("spitterId", 4)

第三,如果再次使用类似于 上一个示例那样校验返回数据的方法校验$.data.records[0],将会得到一个错误。原因也和前文描述的一样。我们必须使用更为稳妥的方法。

第四,对于同一个控制器的测试,我们可以预先做一些设置,比如依赖@BeforeEach注解,约定好一些通用的内容:

 1class MyWebTests {
 2
 3    MockMvc mockMvc;
 4
 5    @BeforeEach
 6    void init(){
 7        mockMvc = standaloneSetup(spittleController)
 8                .alwaysExpect(status().isOk())
 9                .alwaysExpect(content().contentType(MediaType.APPLICATION_JSON))
10                .build();
11    }
12}

上述方法在每一个测试之前准备mockMvc对象,并且约定了servlet的返回状态和返回类型。

2.2.3 POST请求方法测试 #

如前所述,在发起POST请求时,一般使用JSON,此时MappingJasksonHttpMessageConverter便会介入。它负责将JSON对象反序列化为控制器指定的Java Baean。在使用MockMvc进行测试时,我们直接使用JSON字符串,将其设置在请求体中即可。

 1@Test
 2public void postSpittlesTimeLinePageTest() throws Exception {
 3    // ... 省略其他设置
 4    dto.setLeftTime(LocalDateTime.parse("2012-06-09T00:00:00.000"));
 5    dto.setRightTime(LocalDateTime.parse("2012-06-09T23:59:59.999"));
 6
 7    when(spittleService.pageQuerySpittlesByTimeLine(any(SpittleDTO.class))).thenReturn(page);
 8
 9    // perform post request
10    String s = objectMapper.writeValueAsString(dto);
11    log.info("request body: {}", s);
12    ResultActions resultActions = mockMvc
13        .perform(post("/spittle/range/spittles")
14            .contentType(MediaType.APPLICATION_JSON)
15            .characterEncoding("utf8")
16            .content(s))
17        .andDo(print());
18
19    // 以下用来获取MockMvc返回(Json)
20    String jsonResult = resultActions.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
21    log.info(jsonResult);
22
23    PageDomain<SpittleVO> rpg = jsonPathParser(jsonResult).read("$.data", PageDomain.class);
24    assertEquals((int) jsonPathParser(jsonResult).read("$.data.records.length()"), 1);
25    SpittleVO rvo = jsonPathParser(jsonResult).read("$.data.records[0]", SpittleVO.class);
26    rpg.setRecords(new ArrayList<SpittleVO>() {{
27        add(rvo);
28    }});
29    assertEquals(rpg, pageDomain);
30}

可以看到,发起POST请求的方式比较简单:

1 ResultActions resultActions = mockMvc
2        .perform(post("/spittle/range/spittles")
3            .contentType(MediaType.APPLICATION_JSON)
4            .characterEncoding("utf8")
5            .content(s));

设置好请求头接受的文件类型和编码,使用content(json)方法传入json字符串即可。

在本节的 开头,我们进行了一些通用的配置,你可能暂时还没有注意到这个细节:

1//    @Autowired
2//    protected ObjectMapper objectMapper;
3    protected ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();

我们注释掉了spring自动装配的ObjectMapper,转而使用了Jackson2ObjectMapperBuilder构建了一个默认的ObjectMapper,这样做是有原因的:

对于spring自动装配的ObjectMapper,我们在项目改变了其对LocalDateTime的序列化与反序列化规则:

对于LocalDateTime,默认情况下其字符串输出格式类似于2012-06-09T23:59:59.999,这样的字符串形式非常不利于页面传递参数,因此我们在项目配置中改变了其规则,使得在实际使用时,能够将2012-06-09 23:59:59.999形式的日期字符串直接转化为LocalDateTime对象;反之,LocalDateTime也将会直接转化为2012-06-09 23:59:59.999的形式返回。

但是在使用MockMvc进行测试时,其进行反序列化时(将请求JSON转化为Java Bean),使用的可能是默认的消息转换规则。而当我们使用自动装配的ObjectMapper将配置好的Bean转化为JSON时,时间的字符串形式是2012-06-09 23:59:59.999,默认的消息转换无法将其转化为LocaldateTime,因此会出现转换异常

关于ObjactMapper的详细内容,会在后续博客中详细介绍。

3 JsonPath #

看到这里,你可能对使用Mockito和MockMvc进行测试有了初步的了解。不过如果你细心的话,就会发现,前面的测试用例对最后的接口的返回校验都没有提及。并且示例代码中关于提取返回内容出现最多的字就是jsonPath

并且在前面的测试用例中,我们也通过简单的表达式jsonPath("$.data")提取了返回JSON中的结果。

实际上,Spring MockMvc默认是支持使用JsonPath获取返回内容的,就像jsonPath("$.data")那样,不过其灵活性没有直接使用JspnPath大,特别是在反序列化的操作上。

很多时候,RESTful接口返回的内容实际上是Java Bean序列化之后的JSON串,所以我们希望将获取到的JSON反序列化之后再进行校验,而MockMvc在这方面表现的就比较蹩脚了,其只能转化为Map进行比较,就像 ###2.2.1节中表现的那样3

说实话,测试在获取到返回的JSON串,通过控制台打印输出确认符合预期基本上就可以结束,再去检验JSON的内容有点强迫症的意味了。

其实在 JsonPath的仓库里详细地介绍了JsonPath的基本用法,针对本实例的具体情况,通过阅读文档,我们可以很容易取得想要的值并进行校验。

 1{
 2    "code":20000,
 3    "msg":"http.ok",
 4    "data":
 5        {
 6            "currentPage":1,
 7            "pageSize":10,
 8            "total":4,
 9            "pages":1,
10            "records":
11                [
12                    {
13                    "id":1,
14                    "spitterId":4,
15                    "message":"sixth man",
16                    "time":"2012-06-09 22:20:00",
17                    "latitude":0.0,
18                    "longitude":0.0
19                    }
20                ]
21        }
22}

我们要校验的就是data中的内容,现在我们再回过头来看看上面的 测试代码,实际上很容易理解:

1PageDomain<SpittleVO> rpg = jsonPathParser(jsonResult).read("$.data", PageDomain.class);
2    assertEquals((int) jsonPathParser(jsonResult).read("$.data.records.length()"), 1);
3    SpittleVO rvo = jsonPathParser(jsonResult).read("$.data.records[0]", SpittleVO.class);
4    rpg.setRecords(new ArrayList<SpittleVO>() {{
5        add(rvo);
6    }});
7    assertEquals(rpg, pageDomain);

首先我们获取了$.data节点的内容,里面也是一个Json对象,按照传统的反序列化理解,这是一个Java Bean,我们该如何将其读取为我们程序中的Bean呢,JsonPath也 作了说明

默认情况下,通过JsonPath.parse(json).read("$.data")获取的到的是Map实例,并不会映射为Java Bean。不过JsonPath也为此提供了可能:

If you configure JsonPath to use JacksonMappingProvider or GsonMappingProvider you can even map your JsonPath output directly into POJO's.

要想映射为JavaBean,我们需要:

  • 自定义配置JsonProvider
  • 传入类型参数

配置JsonProvider的方式也很简单:

 1/**
 2    * Use json-path, tweaking configuration<br>
 3    * The config below change default action of json-path<br>
 4    * Use application-context ObjectMapper config as json and mapper provider<br>
 5    * <p>
 6    * Reference: <a href="https://github.com/json-path/JsonPath">
 7    * https://github.com/json-path/JsonPath</a>
 8    *
 9    * @param json standard json string
10    * @return {@link DocumentContext}
11    */
12protected DocumentContext jsonPathParser(String json) {
13
14    final JsonProvider jsonProvider = new JacksonJsonProvider(objectMapper);
15    final MappingProvider mappingProvider = new JacksonMappingProvider(objectMapper);
16    Configuration.setDefaults(new Configuration.Defaults() {
17        @Override
18        public JsonProvider jsonProvider() {
19            return jsonProvider;
20        }
21
22        @Override
23        public Set<Option> options() {
24            return EnumSet.noneOf(Option.class);
25        }
26
27        @Override
28        public MappingProvider mappingProvider() {
29            return mappingProvider;
30        }
31    });
32    return JsonPath.parse(json);
33}

此时,我们就已经获取到了接口返回的对象。不过等等,我们再仔细看看上面的Json,会发现$.data.records节点是一个数组,数组里面又是可以映射为Java Bean的Json。而经过上一步,获取的PageDomain对象中的records域实际上还是一个List<Map>的默认映射结果,所以我们还需要梅开二度。

4 补充内容:使用idea直接进行RESTful接口测试 #

到这里,本文的主要内容就结束了。

如果你使用的IDEA,你不妨找找tools->httpClients,你会发现,idea的绝妙功能:其可以通过脚本文件测试rest接口。

idea提供了不同HTTP请求的脚本示例,很容易就能上手,脚本文件以.http结尾,你可轻松创建自己的测试脚本。

例如,我为上面的测试创建一个名为rest-api.http的脚本:

 1### get spitter info by spitterId
 2GET {{host}}/spitter/{{spitterId}}?lang={{lang}}
 3Accept: application/json
 4
 5
 6### 分页获取spittle, 根据用户spitterId,请求参数放在GET请求体中的情形:
 7GET {{host}}/spittle/user/spittles?lang={{lang}}
 8Accept: application/json
 9Content-Type: application/json
10
11{
12  "spitterId": 4,
13  "currentPage": 1,
14  "pageSize": 1
15}
16
17
18### 分页获取某个时间段的spittle 1
19POST {{host}}/spittle/range/spittles?lang={{lang}}
20Content-Type: application/json
21
22{
23  "leftTime": "2012-06-09 00:00:00",
24  "rightTime": "2012-06-09 23:59:59",
25  "currentPage":2,
26  "pageSize": 1
27}

可以看到,.http脚本文件的可读性非常强。其中,为了方便,还使用了用双花括号语法的环境变量,这些变量被命名在一个名为http-client-env.json的json文件中:

 1{
 2  "mem": {
 3    "host":"http://localhost:9000/mem",
 4    "spitterId": 4,
 5    "lang": "en",
 6    "currentPage": 1,
 7    "pageSize": 2
 8  },
 9  "mysql":{
10    "host": "http://localhost:9100/dev",
11    "spitterId": 2,
12    "lang": "en"
13  }
14}

运行脚本时,可以通过执行环境配置传入不同的测试参数,就这么简单。


参考 #

  • 本文用例所在项目地址:https://www.github.com/wangy325/mybatis-plus-starter
  • mockito官网:https://www.site.mockito.org/
  • mockito API官网:https://www.javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
  • MockMvc java doc:https://www.docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/MockMvc.html
  • json-path仓库介绍了其基本使用方法:https://www.github.com/json-path/JsonPath
  • 可能出现的bug:https://www.stackoverflow.com/questions/47276920/mockito-error-however-there-was-exactly-1-interaction-with-this-mock
  • MockMvc官方文档:https://www.docs.spring.io/spring-framework/docs/current/reference/html/testing.html#spring-mvc-test-framework
  • MockMVC官方测试示例代码库:https://www.github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/servlet/samples

  1. 这种形式的比较往往会出现问题,例如,如果pojo类中的id字段定义为Long型,使用objectMapper进行转换的时候可能会转换为Integer型。 ↩︎

  2. 关于Spring MVC的消息转换器,参考《Spring实战,第4版》第16章相关内容。 ↩︎

  3. 或许笔者还没有找到更加优雅的方法。 ↩︎