java服務器端Mock服務接口模擬實踐入門


Mock服務的使用目的在於前端測試、APP開發、前端測試人員在服務還沒完備時模擬接口。

本篇里實現實時動態mock的完整代碼:

https://gitee.com/475660/databand/tree/master/databand-mock-api

而不是傳統使用靜態mock,每次都要手動配置json,還要重新啟動mock服務的方式。

如圖,用戶服務、其他服務沒交付,賬單服務交付了。那么app就通過mock模擬用戶服務、其他服務接口。賬單服務經mock服務中轉,或者直連。

分類:

  • 客戶端mock:mockjs
  • 服務端mock:mockserver、moco

mockjs
http://mockjs.com/

mockserver
https://github.com/mock-server/mockserver

moca
https://github.com/dreamhead/moco

模擬方式

客戶端mock主要模擬:

  • 1. 瀏覽器客戶端Js-URL攔截
  • 2. 模擬數據

服務端mock:

  • 1. 提供真正mock-http服務
  • 2. 攔截request請求,判斷請求的分支(通過程序或配置文件)
  • 3. 返回response,由mock-http服務提供響應數據
  • 4. 或者forward,跳到其他服務url

 

mockserver使用

MockServer運行方式:

  • . 通過java程序(我們的演示程序使用這種方式)
  • . JUnit環境
  • . 命令行(缺點是不能像moco那樣帶配置文件參數,這個下面將講到)
  • . maven plugin
  • . Node.js
  • . war包

配置項

Request-Matcher 匹配的參數:

  • 1. method - 有GET\POST\PUT\DELETE...
  • 2. path - 就是請求路徑,比如\mypath\{id}
  • 3. path parameters - 路徑參數
  • 4. query string parameters - 直接的QueryString請求參數
  • 5. headers - key-values matchers
  • 6. cookies - key-values matchers
  • 7. body - body matchers
  • 8. secure - boolean value, true for HTTPS

response返回以下任意:

  • 1. status code
  • 2. reason phrase
  • 3. body
  • 4. headers
  • 5. cookies
  • 6. delay

#1.簡單get請求

private static void mockGet() {        
        mockServer.when(
                request()
                    .withMethod("GET")
                    .withPath("/cart")
                    //設置兩次后失效
                    //,Times.exactly(2)
                    //設置5秒內只能訪問一次
                    //,Times.once(),
                    //TimeToLive.exactly(TimeUnit.SECONDS, 5L)
                    //正則,starts with "/some"
                    //.withPath("/some.*")//.withPath(not("/some.*"))
            )
            .respond(
                response()
                    .withBody("some_response_body")
            );
        
        mockServer.when(
            request()
                .withMethod("GET")
                .withPath("/cart1")
                .withQueryStringParameters(
                        new Parameter("cartId", "055CA455-1DF7-45BB-8535-4F83E7266092")
                )
        )
        .respond(
            response()
                .withBody("some_response_body_withQueryStringParameters")
        );
        
    } 

#2.參數正則替換#

private static void mockGetPathParameter() {
        
        mockServer.when(
                request()
                    .withMethod("GET")
                    .withPath(BASEPATH_VIEW+"/cart/{cartId}")
                    .withPathParameters(
                        new Parameter("cartId", "[A-Z0-9\\-]+")
                    )
                    .withQueryStringParameters(
                            new    Parameter("year", "2019"),
                            new    Parameter("month", "10"),
                            new    Parameter("userid", "[A-Z0-9\\\\-]+"))
            )
            .respond(
                response()
                    .withBody("some_response_body")
            );    

    }

#3.POST request with Body-json#

public static void mockPost() {

        mockServer.when(
            request()
                .withMethod("POST")
                .withPath(BASEPATH_VIEW)
                .withBody("{username: 'user', password: 'mypassword'}")
        )
            .respond(
                response()
                .withStatusCode(200)
                .withCookie(
                        "sessionId", "2By8LOhBmaW5nZXJwcmludCIlMDAzMW"
                        )
                .withHeaders(
                        new Header("Content-Type", "application/json; charset=utf-8"),
                        new Header("Cache-Control", "public, max-age=86400")
                        )
                .withBody("{ \"apply_id\": \"000001\", \"overdued\": \"Y\" }")
        );
    }

#4.json占位符

        private static void mockBodyPlaceholder() {
            mockServer.when(
                request()
                .withBody(
                        new JsonBody("{" + System.lineSeparator() +
                                "    \"id\": 1," + System.lineSeparator() +
                                "    \"name\": \"A_${json-unit.any-string}\"," + System.lineSeparator() +
                                "    \"price\": \"${json-unit.any-number}\"," + System.lineSeparator() +
                                "    \"price2\": \"${json-unit.ignore-element}\"," + System.lineSeparator() +
                                "    \"enabled\": \"${json-unit.any-boolean}\"," + System.lineSeparator() +
                                "    \"tags\": [\"home\", \"green\"]" + System.lineSeparator() +
                                "}",
                            MatchType.ONLY_MATCHING_FIELDS
                        )
                )
                )
            .respond(
                response()
                    .withBody("some_response_body")
        );
    }

#5.Responese with body-json

private static void mockResponese() {
        mockServer.when(
                request()
                    .withMethod("GET")
                    .withPath(BASEPATH_GET)
            )
        .respond(
                response()
                .withStatusCode(200)
                //.withHeader("Content-Type", "plain/text")
                .withCookie("Session", "97d43b1e-fe03-4855-926a-f448eddac32f")
                .withBody(new JsonBody("{" + System.lineSeparator() +
                        "    \"id\": 1," + System.lineSeparator() +
                        "    \"name\": \"姓名\"," + System.lineSeparator() +
                        "    \"price\": \"123\"," + System.lineSeparator() +
                        "    \"price2\": \"121\"," + System.lineSeparator() +
                        "    \"enabled\": \"true\"," + System.lineSeparator() +
                        "    \"tags\": [\"home\", \"green\"]" + System.lineSeparator() +
                        "}"
                    ))
            );
    }

#6.一個稍微靈活些的擴展思路:通過把各種配置項寫進數據表,注入到mock服務,實現無代碼mock

static ClientAndServer mockServer;

    public static void main(String[] args) {
        
        mockServer = startClientAndServer(1080);
        List<Instance> mockInstances = getDataFromDb();
        mockInstances(mockInstances);
    }

    private static List<Instance> getDataFromDb() {
        List<Instance> mockInstances = new ArrayList<Instance>();
        
        //數據行1
        Instance inst = new Instance();        
        inst.setMethod("GET"); 
        inst.setPath("/mypath/{id}/{type}");

        Map<String,String> nullMap = new HashMap<String,String>();
        Parameter[] paths = {new Parameter("id", "[A-Z0-9\\\\-]+"),new Parameter("type", "[A-Z0-9\\\\-]+")};
        //inst.setQuery_string_parameters(new Parameter("year", "2019")); 
        inst.setPath_parameters(paths);
        inst.setReq_cookie(nullMap);//--留空
        inst.setReq_headers(nullMap);//--留空
        inst.setReq_jsonbody("{}");//--空json
        inst.setResp_body("{\r\n" + 
                "  \"id\" : 1,\r\n" + 
                "  \"name\" : \"姓名\",\r\n" + 
                "  \"price\" : \"123\",\r\n" + 
                "  \"price2\" : \"121\",\r\n" + 
                "  \"enabled\" : \"true\",\r\n" + 
                "  \"tags\" : [ \"tag1\", \"tag2數組項\" ]\r\n" + 
                "}");
        
        Map<String,String> cookieMap = new HashMap<String,String>();        
        cookieMap.put("Session", "97d43b1e-fe03-4855-926a-f448eddac32f");
        inst.setResp_cookie(cookieMap);
        inst.setResp_headers(nullMap);
        inst.setResp_statuscode(200);
        
        //數據行2
        Instance inst2 = new Instance();
        inst2.setMethod("GET"); 
        inst2.setPath("/mypath2");
        Parameter[] queryParams = {new Parameter("month", "10"),new Parameter("userid", "[A-Z0-9\\\\-]+")};
        inst2.setQuery_string_parameters(queryParams); 
        inst2.setReq_cookie(nullMap);//--留空
        inst2.setReq_headers(nullMap);//--留空
        inst2.setReq_jsonbody("{}");//--空json
        inst2.setResp_body("{\r\n" + 
                "  \"id\" : \"97d43b1e-fe03-4855-926a-f448eddac32f\",\r\n" + 
                "  \"name\" : \"姓名\",\r\n" + 
                "  \"year\" : \"2019\",\r\n" + 
                "  \"month\" : \"10\",\r\n" + 
                "  \"userid\" : \"id\",\r\n" + 
                "  \"tags\" : [ \"tag3\", \"tag4\" ]\r\n" + 
                "}");
        inst2.setResp_cookie(cookieMap);
        inst2.setResp_headers(nullMap);
        inst2.setResp_statuscode(200);
        
        //把數據行注入實例列表,用於后續注入
        mockInstances.add(inst);
        mockInstances.add(inst2);
        return mockInstances;
    }


    private static void mockInstances(List<Instance> mockInstances) {
        
        for (Instance inst : mockInstances) {
            List<Cookie> cookies = new ArrayList<Cookie>();
            List<Header> headers = new ArrayList<Header>();
            List<Cookie> resp_cookies = new ArrayList<Cookie>();            
            List<Header> resp_headers = new ArrayList<Header>();                    
            //注入MOCK
            injectionMock(inst, cookies, headers, resp_cookies, resp_headers);        
        }
    }

    private static void injectionMock(Instance inst, List<Cookie> cookies, List<Header> headers,
            List<Cookie> resp_cookies, List<Header> resp_headers) {
        for (Entry<String, String> entry : inst.getReq_cookie().entrySet()) {
            cookies.add(new Cookie(entry.getKey(),entry.getValue()));
        }            
        for (Entry<String, String> entry : inst.getReq_headers().entrySet()) {
            headers.add(new Header(entry.getKey(),entry.getValue()));
        }
        
        for (Entry<String, String> entry : inst.getResp_cookie().entrySet()) {
            resp_cookies.add(new Cookie(entry.getKey(),entry.getValue()));
        }
        for (Entry<String, String> entry : inst.getResp_headers().entrySet()) {
            resp_headers.add(new Header(entry.getKey(),entry.getValue()));
        }
        
        if (inst.getPath_parameters()==null) {                
            mockQueryParamsNoBody(inst,cookies, headers, resp_cookies, resp_headers);
        }
        else {
            mockPathParamsNoBody(inst,cookies, headers, resp_cookies, resp_headers);
        }
    }

    private static void mockPathParamsNoBody(Instance inst, List<Cookie> cookies, List<Header> headers,
            List<Cookie> resp_cookies, List<Header> resp_headers) {
        
        mockServer.when(
                request()
                    .withMethod(inst.getMethod())
                    .withPath(inst.getPath())
                    .withPathParameters(inst.getPath_parameters())
                    //.withQueryStringParameters(inst.getPath_parameters())
                    .withCookies(cookies)
                    .withHeaders(headers)
                    .withBody(new JsonBody(inst.getReq_jsonbody()))
            )
        .respond(
                response()
                .withStatusCode(inst.getResp_statuscode())
                .withHeaders(resp_headers)
                .withCookies(resp_cookies)
                .withBody(new JsonBody(
                        inst.getResp_body()    
                    ))
            );
    }
    
    private static void mockQueryParamsNoBody(Instance inst, List<Cookie> cookies, List<Header> headers,
            List<Cookie> resp_cookies, List<Header> resp_headers) {
        
        mockServer.when(
                request()
                    .withMethod(inst.getMethod())
                    .withPath(inst.getPath())
                    //.withPathParameters(inst.getPath_parameters())
                    .withQueryStringParameters(inst.getQuery_string_parameters())
                    .withCookies(cookies)
                    .withHeaders(headers)
                    .withBody(new JsonBody(inst.getReq_jsonbody()))
            )
        .respond(
                response()
                .withStatusCode(inst.getResp_statuscode())
                .withHeaders(resp_headers)
                .withCookies(resp_cookies)
                .withBody(new JsonBody(
                        inst.getResp_body()    
                    ))
            );
    }

 

結果:
調用:http://localhost:1080/mypath/22/2234

{
      "id" : 1,
      "name" : "姓名",
      "price" : "123",
      "price2" : "121",
      "enabled" : "true",
      "tags" : [ "tag1", "tag2數組項" ]
     }


調用:http://localhost:1080/mypath2?month=10&userid=11

{
"id" : "97d43b1e-fe03-4855-926a-f448eddac32f",
"name" : "姓名",
"year" : "2019",
"month" : "10",
"userid" : "id",
"tags" : [ "tag3", "tag4" ]
}

 

moco使用


開發者鄭曄是ThoughtWorks首席技術專家。moco也是根據一些配置,啟動一個HTTP服務。當發起請求滿足配置中的一個條件時,它就給回復一個應答。Moco的底層沒有依賴於像Servlet這樣的重型框架,是基於Netty框架直接編寫的HTTP服務,這樣一來,繞過了復雜的應用服務器,速度極快。

Moco運行方式:
https://github.com/dreamhead/moco/blob/master/moco-doc/usage.md 

  • 1. API通過java程序
  • 2. JUnit環境
  • 3. 命令行(帶配置文件參數,這個好用)
  • 4. maven plugin
  • 5. Socket
  • 6. https

最簡單用法

conf.json

conf.json  
    [
          {
            "response" :
                  {
                    "text" : "Hello, Moco"
                  }
              }
     ] 

run:

java -jar moco-runner-1.1.0-standalone.jar http -p 12306 -c conf.json

例子 

[
      {
        "request" :
        {
          "uri":"/getUser",
          "method":"get",
          "queries":{
            "type":"pp",
            "age":"3"
          }
        },
        "response" :
          {
            "text" : "Hello, Moco"
          }
      },
    
      {
        "description":"帶參數的post請求",
        "request":{
          "uri":"/postDemoWithParam",
          "method":"post",
          "forms":{
            "param1":"one",
            "param2":"two"
          }
        },
        "response":{
          "text":"this is post request with param 並且是form格式的"
        }
      },
    
      {
        "description":"帶header請求",
        "request": {
          "uri": "/withHeader",
          "method": "post",
          "headers": {
            "content-type": "application/json"
          },
          "json": {
            "name": "xiaoming",
            "age": "18"
          }
        },
        "response": {
          "json": {
            "code": "C0",
            "msg": "查詢成功",
            "data": {
              "name": "周**",
              "cid": "22************011",
              "respCode": "0",
              "respDesc": "一致,成功",
              "detail": {
                "name": "周**",
                "cid": "22************011",
                "driveIssueDate": "A",
                "driveValidStartDate": "A",
                "firstIssueDate": "A",
                "validDate": "-B",
                "driveCardStatus": "A",
                "allowDriveCar ": "C小型汽車 ",
                "driveLicenseType ": "A ",
                "gender ": "1 / 男性 "
              }
    
            }
          }
        }
      },
    ]

如果response響應json非常長,可以寫到文件里

{
        "description":"查全部",
        "request":{
          "uri":"/findAll",
          "forms":{
            "gender": "1"
          }
        },
        "response":{
          "file":"result_file/findAll.json"
        }
      }

分模塊

當配置的路徑多了,容易亂,可以分模塊,比如首頁模塊、登陸模塊

首頁模塊:index.json:

 [
            {
            "description": "首頁",
            "request": {
            "uri": "/index"
            },
            "response": {
            "text": "hello world"
            }
            }
        ]

登錄模塊:login.json

[
    {
        "description": "登錄",
        "request": {
            "uri": "/login"
        },
        "response": {
            "text": "success"
        }
    }
   ] 

合並:

[
{"include": "index.json"},
{"include": "login.json"}
]

 

MockJS客戶端mock就不詳細介紹了,非常簡單,可以看官網說明。

實現實時動態mock的完整代碼:

https://gitee.com/475660/databand/tree/master/databand-mock-api

 


免責聲明!

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



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