在SpringBoot项目中使用MockMvc进行接口测试
现在流行在项目中使用 swagger对接口进行测试,这确实很方便、直观。
但是MockMvc作为spring-test包中指定的测试框架,在没有使用swagger的项目中,使用其进行测试是很好的选择。
本文简单介绍在springboot项目中使用 Mockito和 MockMvc对控制器进行测试。
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的内容,可参考:
- https://javadoc.io/static/org.mockito/mockito-core/3.8.0/org/mockito/ArgumentMatchers.html
- https://javadoc.io/static/org.mockito/mockito-core/3.8.0/org/mockito/hamcrest/MockitoHamcrest.html
除了使用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
方法比较的对象的相等性,因此可以获取绑定的返回值。
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¶2=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