第九节:单元测试 -- controller -- save
我们在上节中学习对如何测试get
请求。post
的请求与get
请求基本相同。
我们使用postMan
来查看下,get
与post
生成的实际数据都有什么。
get:
post:
通过对上面两张图片的对比,不难发现,post
与get
除了请求方式不同外,还需要指定了一个content type
类型,另外,还需要加入发送的json
数据。
本节中,让我们共同学习SpringMVC
是怎么解决这两点内容的。
content type
@Test
public void save() throws Exception {
// 提交POST请求,并设置contentType
this.mockMvc
.perform(post("/klass/")
.contentType("application/json"))
.andDo(print());
}
测试:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /klass/
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8]}
正如我们看到的一样,在调用contentType()
,mockMvc
在提交请示时,为我们设置了Content-Type
的值,同时,还添加了系统的默认编码。
json 数据
在使用postMan
的过程中,我们可以尝试点击body
标签中的各个选项卡,并输入一些测试数据,最后查看code
。你会发现,各个不同类型反映至最终发送的数据上,一是content type
值不同,二就是发送数据的格式不同。但本质上,都是在发送普通字符串。
json数据也是特定格式的定符串。
比如我们增加班级信息时发送的数据为:{"name":"hello","teacherId":1}
。mockMvc
为我们提供了content()
函数,用于发送主体数据。
public void save() throws Exception {
// 提交POST请求,并设置contentType
this.mockMvc
.perform(post("/klass/")
.contentType("application/json")
.content("{\"name\":\"hello\",\"teacherId\":1}"))
.andDo(print());
}
\"
用于转义"
,转义后,"
将做为一个普通的字符串来处理。
测试:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /klass/
Parameters = {}
Headers = {Content-Type=[application/json;charset=UTF-8]}
Handler:
Type = com.mengyunzhi.controller.KlassController
Method = public com.mengyunzhi.repository.Klass com.mengyunzhi.controller.KlassController.save(com.mengyunzhi.controller.KlassController$JsonInput)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[application/json;charset=UTF-8]}
Content type = application/json;charset=UTF-8
Body = {"id":1,"teacher":null,"name":"hello"}
Forwarded URL = null
Redirected URL = null
Cookies = []
完善测试代码
基本的用法学习后,我们完善测试代码。
注释:
public void save() throws Exception {
// 获取一个有效的教师
// 提交JSON POST请求, 断言收到json数据,并存在id值
}
添加代码:
import static org.hamcrest.Matchers.notNullValue;
public void save() throws Exception {
// 获取一个有效的教师
Klass klass = klassService.getNewKlass();
Long teacherId = klass.getTeacher().getId();
// 提交JSON POST请求, 断言收到json数据,并存在id值
this.mockMvc
.perform(post("/klass/")
.contentType("application/json")
.content("{\"name\":\"hello\",\"teacherId\":" + teacherId.toString() + "}"))
.andDo(print())
.andExpect(jsonPath("$.id", notNullValue()));
}
测试:
异常测试
在进行单元测试前,我们先回忆下使用postMan
进行测试时可能发生的异常:
name
为空发生的数据绑定校验异常:
name
过长发生的数据绑定校验异常:
缺少必要参数时发生的数据绑定校验异常:
传入字符串非json格式时,发生的json
数据读取异常:
我们并不关心异常的具体类型,我们关心的是:当输入错误的信息时是否发生异常。通过观察不然发现,上述各个异常类型虽然不同,但返回的状态码均为400
。没错,我们就是利用这个特点,来检测是否发生了用户请求的异常。
测试异常
班级名称为空:
@Test
public void saveWithNameEmpty() throws Exception {
// 直接添加一个实体
Klass klass = klassService.getNewKlass();
Long teacherId = klass.getTeacher().getId();
// 检测异常(用户名过短)
this.mockMvc
.perform(post("/klass/")
.contentType("application/json")
.content("{\"name\":\"\",\"teacherId\":" + teacherId.toString() + "}"))
.andDo(print())
.andExpect(status().is4xxClientError());
}
代码分析:使用status().
代表返回状态,is4xxClientError()
代表断言是4xx的错误,比如400,401,402都属于4xx。
班级名称过长:
@Test
public void saveWithNameTooLong() throws Exception {
// 直接添加一个实体
Klass klass = klassService.getNewKlass();
Long teacherId = klass.getTeacher().getId();
// 检测异常(用户名过长)
this.mockMvc
.perform(post("/klass/")
.contentType("application/json")
.content("{\"name\":\"sdfsdfsdfsdfsdf\",\"teacherId\":" + teacherId.toString() + "}"))
.andDo(print())
.andExpect(status().is4xxClientError());
}
除使用is4xxClientError()
来判断是否发生4xx错误以外,我们还可以使得is(int status)
,如is(400)
来判断发生的具体的状态码。还可以使用isNotFound()
来判断是否发生404
错误。Spring
的官方API
中,为我们总结了可用的断言方法:https://docs.spring.io/spring/docs/4.3.7.RELEASE/javadoc-api/org/springframework/test/web/servlet/result/StatusResultMatchers.html。
代码重构
当我们开始重复造轮子的时候,就应该思索自己是否应该马上进行代码重构了。
private Klass klass;
private Long teacherId;
@Before
public void before() {
this.klass = klassService.getNewKlass();
this.teacherId = klass.getTeacher().getId();
}
@Test
public void save() throws Exception {
String content = "{\"name\":\"hello\",\"teacherId\":" + teacherId.toString() + "}";
// 提交JSON POST请求, 断言收到json数据,并存在id值
this._save(content).andExpect(jsonPath("$.id", notNullValue()));
}
@Test
// 检测异常(用户名过短)
public void saveWithNameEmpty() throws Exception {
String content = "{\"name\":\"\",\"teacherId\":" + teacherId.toString() + "}";
this._save(content).andExpect(status().is4xxClientError());
}
@Test
// 检测异常(用户名过长)
public void saveWithNameTooLong() throws Exception {
String content = "{\"name\":\"sdfsdfsdfsdfsdf\",\"teacherId\":" + teacherId.toString() + "}";
this._save(content).andExpect(status().is4xxClientError());
}
// 新增数据
private ResultActions _save(String content) throws Exception {
return this.mockMvc.perform(post("/klass/")
.contentType("application/json")
.content(content))
.andDo(print());
}
总结
本节中,我们使用mockMvc
进行模似post
请求,并使用jsonPath("$.id", notNullValue())
来对返回值是否为null
进行断言;触发异常,并使用status().is4xxClientError()
对发生的异常成功进行了断言。
参考: | StatusResultMatchers | MockMvc | ResultActions | MockMvcResultMatchers |