java服务器端Mock服务接口模拟实践入门

java

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

以上是 java服务器端Mock服务接口模拟实践入门 的全部内容, 来源链接: utcz.com/z/394856.html

回到顶部