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