Spring security實戰(3)-----使用Spring MVC編寫RestFul API

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 可以將帶着錯誤信息進入到方法體中,做業務記錄或處理
  • 兩個需要配合使用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章