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 可以將帶着錯誤信息進入到方法體中,做業務記錄或處理
- 兩個需要配合使用