[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 28 - Rotate String

>>:  【Day 28】Cmd 指令很乱,主办单位要不要管一下 (下) - Cmd 指令混淆

【Day07】Git 版本控制 - Sourcetree

什麽是 Sourcetree? 简单来说,就是一个可以用 GUI 介面来管理版本控制内容的软件。 可...

爬虫crawler -- 虾皮购物

许多厂商、卖家都会想知道自己的商品上架到平台贩售时,商品会排名在哪个位置? 大品牌厂商可能有经费每...

Day 24 - 设定开发帐号 HBuilder X - DCloud 注册

Day 24 - 设定开发帐号 HBuilder X - DCloud 注册 HBuilder X ...

[Day26] 制作测试场景

今天又加班了,回到家快速的实现一下脑中想法,但貌似碰到问题... 今日目标 制作简易场景 接下来 接...