iT邦幫忙

2021 iThome 鐵人賽

DAY 28
0
Software Development

Wow ! There is no doubt about Learn Spring framework in a month.系列 第 28

[Day - 28] - 運用Spring MockMvc 邁向自動化測試之路

Abstract

小編先前每個範例都有提供服務(Service)層級的測試案例,但部分開發者會開發許多控制器(Controller),除了可透過小編所帶出的Swagger頁面或Postman工具進行觸發API測試,那如果要走入自動化測試必須獨立一個測試區塊,故小編提出透過Spring 核心所提供的MockMvc進行觸發各API及進行驗證與測試,小編今天將提供根據HTTP API的回覆狀態碼進行驗證,即根據獲取的回覆實體內容(Response Entity)進行資料筆數及內容驗證,並會採用預設的產生其測試報告給開發者做確認,是一套相當不錯的測試套件,相關細節各位請看原理介紹。

Principle Introduction

MockMvc是基於Spring Boot的測試框架,再運用此框架之前須事先配置好三項設定,分別為:1. @RunWith(SpringJUnit4ClassRunner.class),讓測試運行於Spring測試環境。2. @SpringBootTest(classes = ApplicationBoot.class):提供系統的Spring Boot專案的啟動位置。3. @ActiveProfiles({"stag","native"}):配置測試環境位置(dev|stag|prod)組態資訊。在初始化MockMvc測試套件時,因需要其Web API 呼叫需要用到ServletContext實例,而注入後的WebApplicationContext已包含此Web容器,故會將此WebApplicationContext設置入MockMvcBuilders.webAppContextSetup中,即可完成初始化,範例程式碼如下。

  1. Initialize MockMvc Tool
@Ignore
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ApplicationBoot.class)
@ActiveProfiles({"stag","native"})
public class ControllerTestBase extends TestCase {

    protected MockMvc mvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    public ControllerTestBase() {}

    Logger logger = LoggerFactory.getLogger(ControllerTestBase.class);

    @Before
    public void init() {
        logger.info(this.getClass().getName());
        logger.info("------- init test mock API WebApplicationContext -------");
        this.mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
        logger.info("------- init finished test mock API WebApplicationContext --------");
        logger.info("***** API Mock Start *****");


    }


}
  1. 擴展初始化測試控制器(ControllerTestBase),並延伸配置其MockMvc配件
public class TaiwanProductTestSuite extends ControllerTestBase {
  .....
  .....
  .....
}
  1. 測試GET方法,透過mvc.perform設置MockMvcRequestBuilders.get("/v1/taiwan/list")方法,並配置為JSON格式進行處理,並驗證其狀態碼及內容資訊。
    @Test
    @Order(1)
    public void listSeaFoodTask()  throws Exception{
        MvcResult mvcResult = this.mvc.perform(MockMvcRequestBuilders.get("/v1/taiwan/list")
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .accept(MediaType.APPLICATION_JSON_VALUE))
                .andReturn();
        int status = mvcResult.getResponse().getStatus();
        System.out.println("Http status code : " + status);
        assertEquals(200,status);
        String responseText = mvcResult.getResponse().getContentAsString();
        System.out.println("Response result : " + responseText);
        List<SeaFood> seaFoods = new Gson().fromJson(responseText,List.class);
        assertEquals(seaFoods.size(),3);
        System.out.println("[ TEST CASE ] - Verify [GET - TAIWAN] list sea food products SUCCESS.....!");
    }

3-1. 驗證其結果

16:56:07.779  INFO 39884 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ------- init test mock API WebApplicationContext -------
16:56:07.784  INFO 39884 --- [    Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
16:56:07.784  INFO 39884 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
16:56:07.785  INFO 39884 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 1 ms
16:56:07.786  INFO 39884 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ------- init finished test mock API WebApplicationContext --------
16:56:07.786  INFO 39884 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ***** API Mock Start *****
Http status code : 200
Response result : [{"id":"C-0002","name":"Snow Crab","description":"Opilio is the primary species referred to as snow crab.","price":350},{"id":"F-0001","name":"Dragon fish","description":"Gold type for the Dragon fish.","price":250},{"id":"C-0001","name":"King Crab","description":"A taxon of crab-like decapod crustaceans chiefly found in cold seas.","price":300}]
[ TEST CASE ] - Verify [GET - TAIWAN] list sea food products SUCCESS.....!
16:56:07.950  INFO 39884 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ***** API Mock End *****
  1. 測試CREATE方法,透過mvc.perform設置MockMvcRequestBuilders.post("/v1/taiwan/create")方法,並配置請求內容(RequestBody)為convertJsonRequestBody(seaFood))及透過JSON格式進行處理,並驗證其狀態碼及內容資訊。
    @Test
    @Order(3)
    public void createSeaFoodTask() throws Exception {
        SeaFood seaFood = new SeaFood()
                .setId("F-0999")
                .setName("WEI WEI Crab")
                .setDescription("Opilio is the primary species referred to as china WEI WEI crab.")
                .setPrice(555);
        MvcResult mvcResult = this.mvc.perform(MockMvcRequestBuilders.post("/v1/taiwan/create")
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .content(convertJsonRequestBody(seaFood))
                .accept(MediaType.APPLICATION_JSON_VALUE))
                .andReturn();
        int status = mvcResult.getResponse().getStatus();
        System.out.println("Http status code : " + status);
        assertEquals(201,status);
        String responseText = mvcResult.getResponse().getContentAsString();
        System.out.println("Response result : " + responseText);
        SeaFood responseBody = new Gson().fromJson(responseText,SeaFood.class);
        assertEquals(responseBody.getName(),"WEI WEI Crab");
        assertEquals(responseBody.getId(),"F-0999");
        assertEquals(responseBody.getDescription(),"Opilio is the primary species referred to as china WEI WEI crab.");
        assertEquals(responseBody.getPrice(),555);
        System.out.println("[ TEST CASE ] - Verify [CREATE - TAIWAN] sea food SUCCESS .....!");
    }

4-1. 驗證其結果

17:02:27.524  INFO 39900 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ------- init test mock API WebApplicationContext -------
17:02:27.537  INFO 39900 --- [    Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
17:02:27.538  INFO 39900 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
17:02:27.539  INFO 39900 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 1 ms
17:02:27.539  INFO 39900 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ------- init finished test mock API WebApplicationContext --------
17:02:27.540  INFO 39900 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ***** API Mock Start *****
17:02:27.681  INFO 39900 --- [    Test worker] s.s.s.s.SeaFoodRetailerServiceImpl       : Create Taiwan product success ! 
Http status code : 201
Response result : {"id":"F-0999","name":"WEI WEI Crab","description":"Opilio is the primary species referred to as china WEI WEI crab.","price":555}
[ TEST CASE ] - Verify [CREATE - TAIWAN] sea food SUCCESS .....!
17:02:27.704  INFO 39900 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ***** API Mock End *****
  1. 測試PUT方法,透過mvc.perform設置MockMvcRequestBuilders.put("/v1/taiwan/update")方法,並配置請求內容(RequestBody)為convertJsonRequestBody(seaFood))及透過JSON格式進行處理,並驗證其狀態碼及內容資訊。
        SeaFood seaFood = new SeaFood()
                .setId("C-0002")
                .setName("Snow Crab")
                .setDescription("Opilio is the primary species referred to as snow crab.")
                .setPrice((int)(350*0.8));
        MvcResult mvcResult = this.mvc.perform(MockMvcRequestBuilders.put("/v1/taiwan/update")
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .content(convertJsonRequestBody(seaFood))
                .accept(MediaType.APPLICATION_JSON_VALUE))
                .andReturn();
        int status = mvcResult.getResponse().getStatus();
        System.out.println("Http status code : " + status);
        assertEquals(200,status);
        String responseText = mvcResult.getResponse().getContentAsString();
        System.out.println("Response result : " + responseText);
        SeaFood responseBody = new Gson().fromJson(responseText,SeaFood.class);
        assertEquals(responseBody.getName(),"Snow Crab");
        assertEquals(responseBody.getId(),"C-0002");
        assertEquals(responseBody.getDescription(),"Opilio is the primary species referred to as snow crab.");
        assertEquals(responseBody.getPrice(),280);
        System.out.println("[ TEST CASE ] - Verify [UPDATE - TAIWAN] sea food SUCCESS .....!");
    

5-1. 驗證其結果

17:04:54.342  INFO 39908 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ------- init test mock API WebApplicationContext -------
17:04:54.352  INFO 39908 --- [    Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
17:04:54.353  INFO 39908 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
17:04:54.354  INFO 39908 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 1 ms
17:04:54.355  INFO 39908 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ------- init finished test mock API WebApplicationContext --------
17:04:54.356  INFO 39908 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ***** API Mock Start *****
Http status code : 200
Response result : {"id":"C-0002","name":"Snow Crab","description":"Opilio is the primary species referred to as snow crab.","price":280}
[ TEST CASE ] - Verify [UPDATE - TAIWAN] sea food SUCCESS .....!
17:04:54.531  INFO 39908 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ***** API Mock End *****
  1. 測試DELETE方法,透過mvc.perform設置MockMvcRequestBuilders.delete("/v1/taiwan/remove/{id}","F-0001")方法,並透過JSON格式進行處理,並驗證其狀態碼及內容資訊。
        MvcResult mvcResult = this.mvc.perform(MockMvcRequestBuilders.delete("/v1/taiwan/remove/{id}","F-0001")
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .accept(MediaType.APPLICATION_JSON_VALUE))
                .andReturn();
        int status = mvcResult.getResponse().getStatus();
        System.out.println("Http status code : " + status);
        assertEquals(200,status);
        Boolean isDelete = Boolean.valueOf(mvcResult.getResponse().getContentAsString());
        System.out.println("Response result : " + isDelete);
        assertTrue(isDelete);
        System.out.println("[ TEST CASE ] - Verify [DELETE - TAIWAN] sea food SUCCESS .....!");

5-1. 驗證及結果

17:08:26.229  INFO 39915 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ------- init test mock API WebApplicationContext -------
17:08:26.239  INFO 39915 --- [    Test worker] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring TestDispatcherServlet ''
17:08:26.239  INFO 39915 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
17:08:26.240  INFO 39915 --- [    Test worker] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 1 ms
17:08:26.241  INFO 39915 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ------- init finished test mock API WebApplicationContext --------
17:08:26.241  INFO 39915 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ***** API Mock Start *****
Http status code : 200
Response result : true
[ TEST CASE ] - Verify [DELETE - TAIWAN] sea food SUCCESS .....!
17:08:26.311  INFO 39915 --- [    Test worker] s.s.s.controller.ControllerTestBase      : ***** API Mock End *****

完成以上測試流程後,小編提供的測試報告可取得每個測試套裝的測試API數量,並看取最終測試結果,相關測試結果如下。

Follow up

Run test task

gradle test

Run open result html

open ./build/reports/tests/test/index.html

Test Report

API Spec test report
image

Mind-blowing test Staging environment detail
image

Sample source

spring-sample-mockito

Reference Url

SpringMvc框架MockMvc單元測試註解及其原理分析

WebApplicationContext初始化的三種方式


上一篇
[Day - 27] - Spring 環境管理思想與設計
下一篇
[Day - 29] - 深透 Spring Actuator 創造系統服務監視神之眼
系列文
Wow ! There is no doubt about Learn Spring framework in a month.30

尚未有邦友留言

立即登入留言