1.传统服务与RestFul API风格服务的区别
传统服务风格:
- 用url描述行为
- 不管结果是否成功,状态码都返回200,具体参数获取报文信息
RestFul API服务风格:
- 用url描述资源 (传统的是使用url描述行为)
- 使用Http方法描述行为,使用HTTP状态码来表示不同的结果(而不是通过报文来判断成功还是失败)
- 使用json交互数据(请求和响应)
2.使用到的注解和相关技术
@RestController 标明此Controller提供RestFul
@RequestMappering及其变体 映射http请求到java方法
@RequestParam 映射请求参数到Java方法的参数
@PageableDefault 指定分页参数的默认值 –Pageable spring data中的内部对象
@PathVariable 映射url片段到Java方法的参数 –在url声明中使用正则表达式
@JsonView控制json的输出内容
@RequestBody 映射请求体到Java方法参数
日期类型参数的处理 –使用时间戳来交互数据
@Valid注解和BindigResult验证请求参数的合法性并处理效验结果
3.编写第一个RestFul API测试用例
3.1编写一个带有普通参数的RestFul API测试用例
首先在rz-security-demo中的pom.xml中填写依赖:
<!--spring的测试框架-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
然后在src/test/java目录下新建立一个UserControllerTest.java文件
package com.rz.web.controller;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringRunner.class) //表示如何来运行测试用例,使用SpringRunner来执行测试用例
@SpringBootTest //说明整个类都是一个测试用例的类
public class UserControllerTest {
@Autowired
private WebApplicationContext wac;
//伪造的mvc环境
private MockMvc mockMvc;
@Before
public void init(){
mockMvc=MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void whenQuerySuccess() throws Exception {
//perform 发送一个模拟请求
//由于是restful风格,需要写contentType
//andExpect 对请求结果的期望
//jsonPath 解析返回来的json的内容
mockMvc.perform(MockMvcRequestBuilders.get("/user")
.param("name","joon") //当Java方法有@RequestParam注解时候需要加上该参数
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
//认为返回的是一个长度为三的集合
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
}
}
现在执行下结果是404,这是因为这个RestFul API我们还没有编写,现在我们在src/main/java下新建一个包com.rz.controller,文件名为UserController.java
package com.rz.com.rz.controller;
import com.rz.com.rz.entity.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class UserController {
@GetMapping("/user")
public List<User> getAllUser(@RequestParam(required = false,defaultValue = "jojo") String name){
System.out.print(name);
List<User> users=new ArrayList<User>();
users.add(new User());
users.add(new User());
users.add(new User());
return users;
}
}
3.2编写一个带有普通参数的RestFul API测试用例
当业务复杂的情况下,查询条件会有很多,可以构造对象进行查询
在/src/main/java/com/rz/entity下建立一个类
package com.rz.com.rz.entity;
public class UserQueryCondition {
private String name;
private int age;
private int ageTo;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getAgeTo() {
return ageTo;
}
public void setAgeTo(int ageTo) {
this.ageTo = ageTo;
}
}
修改UserController类的方法
@GetMapping("/user")
public List<User> getAllUser(UserQueryCondition userQueryCondition){
//反射的toString工具,用于打印对象参数的值
System.out.println(ReflectionToStringBuilder.toString(userQueryCondition, ToStringStyle.MULTI_LINE_STYLE));
List<User> users=new ArrayList<User>();
users.add(new User());
users.add(new User());
users.add(new User());
return users;
}
修改UserControllerTest类的方法
@Test
public void whenQuerySuccess() throws Exception {
//perform 发送一个模拟请求
//由于是restful风格,需要写contentType
//andExpect 对请求结果的期望
//jsonPath 解析返回来的json的内容
mockMvc.perform(MockMvcRequestBuilders.get("/user")
.param("name","joon") //当Java方法有@RequestParam注解时候需要加上该参数
.param("age","12")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
//认为返回的是一个长度为三的集合
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
}
3.3 编写一个带有普通参数的RestFul API测试用例
修改UserController类的方法
//Pageable pageable 分页对象
//@PageableDefault(page = 3,size = 1,sort = "age,desc")默认分页注解
@GetMapping("/user")
public List<User> getAllUser(UserQueryCondition userQueryCondition,@PageableDefault(page = 3,size = 1,sort = "age,desc") Pageable pageable){
//反射的toString工具,用于打印对象参数的值
System.out.println(ReflectionToStringBuilder.toString(userQueryCondition, ToStringStyle.MULTI_LINE_STYLE));
System.out.println(pageable.getPageNumber());
System.out.println(pageable.getPageSize());
System.out.println(pageable.getSort());
List<User> users=new ArrayList<>();
users.add(new User());
users.add(new User());
users.add(new User());
return users;
}
修改UserControllerTest类的方法
@Test
public void whenQuerySuccess() throws Exception {
//perform 发送一个模拟请求
//由于是restful风格,需要写contentType
//andExpect 对请求结果的期望
//jsonPath 解析返回来的json的内容
mockMvc.perform(MockMvcRequestBuilders.get("/user")
.param("name","joon") //当Java方法有@RequestParam注解时候需要加上该参数
.param("age","12")
//**********分页参数
.param("size","1")
.param("page","3")
.param("sort","age,desc")
//**********分页参数
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
//认为返回的是一个长度为三的集合
//jsonPath:可以在github上搜索相关资料
.andExpect(MockMvcResultMatchers.jsonPath("$.length()").value(3));
}
4.编写第二个RestFul API测试用例
4.1 编写一个带有映射片段的RestFul API测试用例
继续在UserControllerTest类中添加测试方法
/***
* 查询单个用户
*/
@Test
public void whenGetSignInfoSuccess() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.name").value("tom"));
}
在相对应的UserController类中新增方法
@GetMapping("/user/{id}")
public User getUserInfo(@PathVariable(name = "id") String idxxx){
User user=new User();
user.setName("tom");
return user;
}
4.2 编写一个url声明中使用正则表达式的RestFul API测试用例
继续在UserControllerTest类中添加测试方法
/***
* 查询单个用户(url出现限制)
*/
@Test
public void whenGetInfoSuccess() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/user/a")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().is4xxClientError());
}
在相对应的UserController类中新增方法
/***
* 返回用户具体信息(url有正则的限制,只能是数字)
* @param idxxx
* @return
*/
@GetMapping("/user/{id:\\d+}") //
public User getUserInfo(@PathVariable(name = "id") String idxxx){
User user=new User();
user.setName("tom");
return user;
}
4.3 编写一个使用jsonview注解的RestFul API测试用例
@jsonview:可以对controller每个方法对于同一个对象有不同的输出结果,可以指定不返回哪些字段
4.3.1 使用接口来声明多个视图
4.3.2 在值对象的get方法上指定视图
在rz-security-demo项目下的/src/main/java/com/rz/entity/下的User类添加接口声明,并在在值对象的get方法上指定视图
package com.rz.com.rz.entity;
import com.fasterxml.jackson.annotation.JsonView;
public class User {
//简单视图
public interface UserSimpleView{};
//详细视图
public interface UserDetailView extends UserSimpleView {};
private String name;
private String pwd;
@JsonView(UserSimpleView.class)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@JsonView(UserDetailView.class)
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
4.3.3 在Controller方法上指定视图
在rz-security-demo项目下的/src/main/java/com/rz/controller下的UserController类的指定方法中添加@jsonview注解
/***
* 返回所有用户信息
* @param userQueryCondition
* @param pageable
* @return
*/
@JsonView(User.UserSimpleView.class)
@GetMapping("/user")
public List<User> getAllUser(UserQueryCondition userQueryCondition,@PageableDefault(page = 3,size = 1,sort = "age,desc") Pageable pageable){
//反射的toString工具,用于打印对象参数的值
System.out.println(ReflectionToStringBuilder.toString(userQueryCondition, ToStringStyle.MULTI_LINE_STYLE));
System.out.println(pageable.getPageNumber());
System.out.println(pageable.getPageSize());
System.out.println(pageable.getSort());
List<User> users=new ArrayList<>();
users.add(new User());
users.add(new User());
users.add(new User());
return users;
}
/***
* 返回用户具体信息(url有正则的限制,只能是数字)
* @param idxxx
* @return
*/
@JsonView(User.UserDetailView.class)
@GetMapping("/user/{id:\\d+}")
public User getUserInfo(@PathVariable(name = "id") String idxxx){
User user=new User();
user.setName("tom");
return user;
}
我们可以对结果在控制台查看,修改UserControllerTest类的方法:
/***
* 查询单个用户
*/
@Test
public void whenGetSignInfoSuccess() throws Exception {
String result= mockMvc.perform(MockMvcRequestBuilders.get("/user/1")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(MockMvcResultMatchers.status().isOk()) .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("tom"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
5.编写第三个RestFul API测试用例
5.1 编写一个RequestBody注解的RestFul API测试用例
首先在User实体类中添加id字段
private String id;
@JsonView(UserSimpleView.class)
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
然后在UserController类中添加新增用户的方法:
/***
* 创建用户
* @param user
* @return
*/
@PostMapping
public User createUser(@RequestBody User user){
//反射的toString工具,用于打印对象参数的值
System.out.println(ReflectionToStringBuilder.toString(user,ToStringStyle.MULTI_LINE_STYLE));
user.setId("1");
return user;
}
在UserControllerTest类中新增测试方法:
/***
* 创建用户
* 如果返回405 ,表示有该服务,但是不支持此种http访问方式
* @throws Exception
*/
@Test
public void whenCreateSuccess() throws Exception {
String content="{\"name\":\"tom\",\"pwd\":null}";
mockMvc.perform(MockMvcRequestBuilders.post("/user")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"));
}
5.2 编写一个带有日期类型参数的处理的RestFul API测试用例
首先在User实体类中添加birthday字段
@JsonView(UserSimpleView.class)
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
然后在UserControllerTest类中修改新增用户的测试方法:
/***
* 创建用户
* 如果返回405 ,表示有该服务,但是不支持此种http访问方式
* @throws Exception
*/
@Test
public void whenCreateSuccess() throws Exception {
Date data=new Date();
System.out.println("客户端发送的数据:"+data.getTime());
String content="{\"id\":null,\"name\":\"tom\",\"pwd\":null,\"birthday\":"+data.getTime()+"}";
String result= mockMvc.perform(MockMvcRequestBuilders.post("/user")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(content))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.id").value("1"))
.andReturn().getResponse().getContentAsString();
System.out.println(result);
}
6.编写第四个RestFul API测试用例
6.1 编写一个@Valid 注解和BindigResult验证的RestFul API测试用例
目的是为了:为了提取重复的效验逻辑
首先修改User实体类,加上 @NotBlank注解
@NotBlank
private String pwd;
然后在UserControllerTest类中修改新增用户的测试方法:
/***
* 创建用户
* @param user
* @return
*/
@PostMapping
public User createUser(@Valid @RequestBody User user, BindingResult errors){
if(errors.hasErrors()){
errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage()));
}
//反射的toString工具,用于打印对象参数的值
System.out.println(ReflectionToStringBuilder.toString(user,ToStringStyle.MULTI_LINE_STYLE));
user.setId("1");
return user;
}
注意事项:
- @Valid 用于验证字段,其中有一个错误就打回,进入不了方法体
- BindigResult 可以将带着错误信息进入到方法体中,做业务记录或处理
- 两个需要配合使用