Spring Boot RestApi 測試教程 Mock 的使用


測試 Spring Boot Web 的時候,我們需要用到 MockMvc,即系統偽造一個 mvc 環境。本章主要編寫一個基於 RESTful API 正刪改查操作的測試用例。本章最終測試用例運行結果如下:

本項目源碼下載

1 MockMvc 簡介

Spring Boot Web 項目中我們采用 MockMvc 進行模擬測試

方法 說明
mockMvc.perform 執行一個請求
MockMvcRequestBuilders.get("XXX") 構造一個請求
ResultActions.param 添加請求傳值
ResultActions.accept()) 執行一個請求 如MediaType.TEXT_HTML_VALUE
ResultActions.andExpect 添加執行完成后的斷言。 等同於 Assert.assertEquals
ResultActions.andDo 添加一個結果處理器,表示要對結果做點什么事情,比如此處使用MockMvcResultHandlers.print()輸出整個響應結果信息
ResultActions.andReturn 表示執行完成后返回相應的結果。

示例,注意注釋部分與 addExpect 是等效的,就是斷言

 /**
     * 測試 Hello World 方法
     * */
    @Test
    public void hello() throws  Exception{
       MvcResult mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/api/user/hello")
                .param("name","fishpro")
                .accept(MediaType.TEXT_HTML_VALUE)) //perform 結束
                .andExpect(MockMvcResultMatchers.status().isOk()) //添加斷言
                .andExpect(MockMvcResultMatchers.content().string("Hello fishpro"))//添加斷言
                .andDo(MockMvcResultHandlers.print()) //添加執行
                .andReturn();//添加返回

        //下面部分等等與 addExcept 部分
        //        int status=mvcResult.getResponse().getStatus();                 //得到返回代碼
        //        String content=mvcResult.getResponse().getContentAsString();    //得到返回結果
        //        Assert.assertEquals(200,status);                        //等於 andExpect(MockMvcResultMatchers.status().isOk()) //添加斷言
        //        Assert.assertEquals("Hello World",content);            //andExpect(MockMvcResultMatchers.content().string("Hello World"))//添加斷言
    }

圍繞 MockMvc 其實就是幾個核心的問題

  1. 如何初始化對應類的mockMvc
  2. 如何建立請求包括請求的 url、請求的c ontentType
  3. 如何發送請求參數,包括如何發送 url 參數、form 參數、 json 參數、xml 參數
  4. 如何編寫斷言判斷
  5. 如何打印信息
  6. MockMvc本身的返回

2 代碼實例

本項目主要使用 SpringBoot RESTful API 架構風格實踐 代碼。你可以下載此代碼,也可以重新新建一個 Spring Boot 項目用於測試。

本項目源碼下載

2.1 創建一個 Spring Boot 項目

2.2 pom.xml 依賴管理

除了 web 引入,其他不需要增加額外的依賴

2.2 編寫 Restful 接口部分

新建包 controller 在 com.fishpro.resttest.controller 下新建 UserController.java ,本示例中安裝 Restful API 標准編寫了 增刪改查接口。

package com.fishpro.resttest.controller;

import com.fishpro.resttest.domain.UserDO;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;

/**
 * RESTful API 風格示例 對資源 user 進行操作
 * 本示例沒有使用數據庫,也沒有使用 service 類來輔助完成,所有操作在本類中完成
 * 請注意幾天
 *    1.RESTful 風格使用 HttpStatus 狀態返回 GET PUT PATCH DELETE 通常返回 201 Create ,DELETE 還有時候返回 204 No Content
 *    2.使用 RESTful 一定是要求具有冪等性,GET PUT PATCH DELETE 本身具有冪等性,但 POST 不具備,無論規則如何定義冪等性,需要根據業務來設計冪等性
 *    3.RESTful 不是神丹妙葯,實際應根據實際情況來設計接口
 * */
@RestController
@RequestMapping("/api/user")
public class UserController {
    /**
     * 模擬一組數據
     * */
    private List<UserDO> getData(){
        List<UserDO> list=new ArrayList<>();

        UserDO userDO=new UserDO();
        userDO.setUserId(1);
        userDO.setUserName("admin");
        list.add(userDO);

        userDO=new UserDO();
        userDO.setUserId(2);
        userDO.setUserName("heike");
        list.add(userDO);

        userDO=new UserDO();
        userDO.setUserId(3);
        userDO.setUserName("tom");
        list.add(userDO);

        userDO=new UserDO();
        userDO.setUserId(4);
        userDO.setUserName("mac");
        list.add(userDO);

        return  list;
    }

    /**
     * 測試用 參數為 name
     * */
    @RequestMapping("/hello")
    public String hello(String name){
        return "Hello "+name;
    }

    /**
     * SELECT 查詢操作,返回一個JSON數組
     * 具有冪等性
     * */
    @GetMapping("/users")
    @ResponseStatus(HttpStatus.OK)
    public Object getUsers(){
        List<UserDO> list=new ArrayList<>();

        list=getData();

        return list;
    }

    /**
     * SELECT 查詢操作,返回一個新建的JSON對象
     * 具有冪等性
     * */
    @GetMapping("/users/{id}")
    @ResponseStatus(HttpStatus.OK)
    public Object getUser(@PathVariable("id") String id){

        if(null==id){
            return  null;
        }

        List<UserDO> list= getData();
        UserDO userDO=null;
        for (UserDO user:list
                ) {
            if(id.equals(user.getUserId().toString())){
                userDO=user;
                break;
            }
        }

        return userDO;
    }

    /**
     * 新增一個用戶對象
     * 非冪等
     * 返回 201 HttpStatus.CREATED 對創建新資源的 POST 操作進行響應。應該帶着指向新資源地址的 Location 頭
     * */
    @PostMapping("/users")
    @ResponseStatus(HttpStatus.CREATED)
    public Object addUser(@RequestBody UserDO user){

        List<UserDO> list= getData();
        list.add(user);//模擬向列表中增加數據
        return user;
    }

    /**
     * 編輯一個用戶對象
     * 冪等性
     * */
    @PutMapping("/users/{id}")
    @ResponseStatus(HttpStatus.CREATED)
    public Object editUser(@PathVariable("id") String id,@RequestBody UserDO user){
        List<UserDO> list= getData();
        for (UserDO userDO1:list
                ) {
            if(id.equals(userDO1.getUserId().toString())){
                userDO1=user;
                break;
            }
        }

        return user;
    }

    /**
     * 刪除一個用戶對象
     * 冪等性
     * 返回 HttpStatus.NO_CONTENT 表示無返回內容
     * */
    @DeleteMapping("/users/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteUser(@PathVariable("id") String id){
        List<UserDO> list= getData();
        UserDO userDO=null;
        for (UserDO user:list
                ) {
            if(id.equals(user.getUserId().toString())){
                //刪除用戶
                userDO=user;
                break;
            }
        }
    }
}

2.3 編寫測試部分

  1. 新建類 com.fishpro.resttest.UserControllerTests
  2. 給測試類增加測試注解 @RunWith(SpringRunner.class) @SpringBootTest,增加mockMvc私有變量
  3. 在 @Before方法體初始化mocMvc
    /**
     * 初始化 MockMvc
     * */
    @Before
    public void setUp(){
        
        mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
    }
    
  4. 編寫測試方法,在方法名上增加 @Test注解,在方法體內 使用 mockmvc 進行測試
    • mockMvc.perform 執行一個請求
    • MockMvcRequestBuilders.get("") 構造一個請求
    • ResultActions.param 添加請求傳值,注意這里的param只能傳遞 url中的值,@RequestBody 是需要 contentType().content(json二進制)傳遞的

UserControllerTests.java 代碼

/**
 * 本示例針對 Restful API 風格接口做全面的測試用例
 * fishpro at 2019-07-20
 * 注意事項
 *    1.param(name,value) 只能用於 url 參數傳遞,form 表單傳遞
 *    2. @RequestBody 方法,對應使用 .contentType(MediaType.APPLICATION_JSON).content(json 字符串)
 *    3.大部分測試用例失敗的原因是傳遞參數對應的contentType不正確
 * */
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTests {

    private MockMvc mockMvc;//定義一個 MockMvc

    /**
     * 初始化 MockMvc 通過MockMvcBuilders.standaloneSetup 模擬一個 UserController 測試環境,通過build得到一個MockMvc
     * */
    @Before
    public void setUp(){

        mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
    }
    /**
     * 測試 Hello World 方法
     * hello 方法是一個 get 方法,使用了 url 參數傳遞參數 所以使用了 .param 來傳遞參數
     * accept(MediaType.TEXT_HTML_VALUE) 來設置傳遞值接收類型
     * */
    @Test
    public void hello() throws  Exception{
       MvcResult mvcResult= mockMvc.perform(MockMvcRequestBuilders.get("/api/user/hello")
                .param("name","fishpro")
                .accept(MediaType.TEXT_HTML_VALUE)) //perform 結束
                .andExpect(MockMvcResultMatchers.status().isOk()) //添加斷言
                .andExpect(MockMvcResultMatchers.content().string("Hello fishpro"))//添加斷言
                .andDo(MockMvcResultHandlers.print()) //添加執行
                .andReturn();//添加返回

        //下面部分等等與 addExcept 部分
        //int status=mvcResult.getResponse().getStatus();                 //得到返回代碼
        //String content=mvcResult.getResponse().getContentAsString();    //得到返回結果
        //Assert.assertEquals(200,status);                        //等於 andExpect(MockMvcResultMatchers.status().isOk()) //添加斷言
        //Assert.assertEquals("Hello World",content);            //andExpect(MockMvcResultMatchers.content().string("Hello World"))//添加斷言
    }

    /**
     * 測試用戶列表獲取 /users GET
     * */
    @Test
    public void getUsers() throws  Exception{
        mockMvc.perform(MockMvcRequestBuilders.get("/api/user/users")
                .accept(MediaType.APPLICATION_JSON)) //perform 結束
                .andExpect(MockMvcResultMatchers.status().isOk()) //andExpect
                .andDo(MockMvcResultHandlers.print()) //andDo
                .andReturn();//andReturn
    }

    /**
     * 獲取單個用戶信息 /users/3 GET
     * */
    @Test
    public void getUser() throws  Exception{
        mockMvc.perform(MockMvcRequestBuilders.get("/api/user/users/3")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }

    /**
     * 新增單個用戶信息 /users/ POST
     * 注意 addUser 使用了 @RequestBody 方法,對應使用 .contentType(MediaType.APPLICATION_JSON).content(json 字符串)
     * */
    @Test
    public void addUser() throws  Exception{
        mockMvc.perform(MockMvcRequestBuilders.post("/api/user/users")
                .contentType(MediaType.APPLICATION_JSON).content("{ \"userId\": 3,\"userName\": \"tom\"}"))
                .andExpect(MockMvcResultMatchers.status().isCreated())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }


    /**
     * 編輯一個用戶 /users/ PUT
     * */
    @Test
    public void editUser() throws  Exception{
        mockMvc.perform(MockMvcRequestBuilders.put("/api/user/users/3")
                .contentType(MediaType.APPLICATION_JSON).content("{ \"userId\": 3,\"userName\": \"tom\"}"))
                .andExpect(MockMvcResultMatchers.status().isCreated())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }


    /**
     * 刪除一個用戶 /users/ DELETE
     * */
    @Test
    public void deleteUser() throws  Exception{
        mockMvc.perform(MockMvcRequestBuilders.delete("/api/user/users/3")
                .accept(MediaType.APPLICATION_JSON))
                .andExpect(MockMvcResultMatchers.status().isNoContent())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }
}

2.4 運行實例

右鍵測試類 選擇 Run UserControllerTests with Coverage

mockmvc測試圖

2.4 值得注意的幾個問題

1.請求結果為400 406 (httpstatus)
這個原因通常是 請求參數設置不正確,如 json 應該使用

.contentType(MediaType.APPLICATION_JSON).content("{ \"userId\": 3,\"userName\": \"tom\"}"))


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM