Mock服务的使用目的在于前端测试、APP开发、前端测试人员在服务还没完备时模拟接口。

本篇里实现实时动态mock的完整代码:

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

而不是传统使用静态mock,每次都要手动配置json,还要重新启动mock服务的方式。

Java自动化生成Mock代码 java mock_List

如图,用户服务、其他服务没交付,账单服务交付了。那么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

 

Java自动化生成Mock代码 java mock_json_02

Java自动化生成Mock代码 java mock_Java自动化生成Mock代码_03

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

 

关于作者: 王昕 在广州工作生活30余年。十多年开发经验,在Java、即时通讯、NoSQL、BPM、大数据等领域较有经验。