仲夏叶 | Stornado

生命就是用求知的欲望燃烧自己

MockServer 简介

MockServer简介

服务端测试中,被测服务通常依赖于一系列的外部模块,被测服务与外部模块间通过REST API调用来进行通信。要对被测服务进行系统测试,一般做法是,部署好所有外部依赖模块,由被测服务直接调用。然而有时被调用模块尚未开发完成,或者调用返回不好构造,这将影响被测系统的测试进度。为此我们需要开发桩模块,用来模拟被调用模块的行为。最简单的方式是,对于每个外部模块依赖,都创建一套桩模块。然而这样的话,桩模块服务将非常零散,不便于管理。Mock Server为解决这些问题而生,其提供配置request及相应response方式来实现通用桩服务。本文将专门针对REST API来进行介绍Mock Server的整体结构及应用案例。

MockServer支持能力

1. 返回“模拟”响应

2. 执行回调,动态创建响应

3. 返回异常或无效响应

MockServer支持匹配规则

请求属性匹配器使用以下一个或多个属性匹配请求:

属性匹配可以使用:

  • 字符串值
  • 正则表达式值
  • json schema
  • 可选值
    • 用于: 方法、路径、路径参数值、查询参数键、查询参数值、标头键、标头值、Cookie 键、Cookie 值或实体
    • 不支持:路径参数键或值、查询参数值、标头值或 Cookie 值
    • 示例:查询参数、标头、cookie
  • 否定值
    • 用于:方法、路径、路径参数键、路径参数值、查询参数键、查询参数值、标头键、标头值、Cookie 键、Cookie 值或实体
    • 示例:方法路径、标头

匹配多个值的键支持标头、查询参数和路径参数的每个键的多个值

  • 键支持除 json架构之外的所有属性匹配器
  • 值支持除可选值之外的所有属性匹配器
  • 匹配支持两种模式:
    • 子集(默认) - 如果请求属性包含匹配的子集(考虑可选键),则匹配,因此每个非可选键或可选键至少有一个匹配值(如果存在
    • 匹配键- 如果请求属性仅包含匹配值(考虑可选键),则匹配键,因此所有值必须匹配每个非可选键或可选键(如果存在)

匹配密钥与单个值支持Cookie 的每个密钥的单个值

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ssh root@mockserver.zxytech.com

# 拉取镜像 docker pull mockserver/mockserver:mockserver-5.11.1
# 相对于原始镜像 开放“/config”,“/libs”两个Volume便于映射到宿主机,维护规则及自定义jar
docker pull zxytech/mockserver:5.11.1

# 直接使用host网络运行MockServer
docker run \
--network host \ # 使用宿主机网络
-u root:root \ # 使用root避免挂载文件无法在宿主机创建
-d \ #
-v "/home/mockserver/config":"/config" \ # 便于直接在宿主机维护配置文件
-v "/home/mockserver/libs":"/libs" \ # 便于在宿主机维护自定义jar
-v "/etc/localtime":"/etc/localtime" \ # 与宿主机使用相同时区
--name mockserver \ # 命名便于后续使用
zxytech/mockserver:5.11.1 # 使用自定义镜像


# 查看日志
docker logs -f --tail 100 mockserver

# 更新jar包重启服务
scp common-1.0-SNAPSHOT.jar mockserver.zxytech.com:/home/mockserver/libs

docker restart mockserver

配置开机启动

1. 添加文件 /etc/rc.d/init.d/mockserver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/bin/bash
#
# mockserver
# chkconfig: 2345 80 90
# description: start mockserver docker
# config: /home/mockserver/config/mockserver.properties
#
# Copyright 2002 Red Hat, Inc.
#
# Based in part on a shell script by
# Andreas Dilger <adilger@turbolinux.com> Sep 26, 2001

PATH=/sbin:/usr/sbin:$PATH
RETVAL=0


usage ()
{
echo $"Usage: $0 {start|stop|status|restart}" 1>&2
RETVAL=2
}

start ()
{
docker start mockserver
}

stop ()
{
docker stop mockserver
}

status ()
{
docker ps
}


restart ()
{
docker restart mockserver
}



case "$1" in
stop) stop ;;
status) status ;;
start|restart|reload|force-reload) restart ;;
*) usage ;;
esac

exit $RETVAL

2. 添加到开机启动

1
2
chmod +x /etc/rc.d/init.d/mockserver
chkconfig --add mockserver

添加防火墙规则

1. 添加 /usr/lib/firewalld/services/mockserver.xml

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<service>
<short>MockServer</short>
<description>Easy mocking of any system you integrate with via HTTP or HTTPS</description>
<port protocol="tcp" port="1080"/>
<port protocol="udp" port="1080"/>
</service>

2. 永久添加规则

firewall-cmd --permanent --add-service=mockserver

使用示例

使用REST创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PUT http://172.20.66.36:1080/mockserver/expectation HTTP/1.1
Content-Type: application/json

{
"httpRequest" : {
"path" : "/authentication/verify-id",
},
"httpResponse" : {
"body" : {
"code": "0000",
"message": "操作成功"
}
}
}

Dockerfile

1
2
3
docker image build -t zxytech/mockserver:5.11.1 .

docker push zxytech/mockserver

https://www.mock-server.com/mock_server/running_mock_server.html#docker_container

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#
# MockServer Dockerfile
#
# https://github.com/mock-server/mockserver
# http://www.mock-server.com
#

# build image
FROM alpine as build

# download jar
RUN apk add --update openssl ca-certificates bash wget
# REPOSITORY is releases or snapshots
ARG REPOSITORY=releases
# VERSION is LATEST or RELEASE or x.x.x
ARG VERSION=RELEASE
# see: https://oss.sonatype.org/nexus-restlet1x-plugin/default/docs/path__artifact_maven_redirect.html
ARG REPOSITORY_URL=https://oss.sonatype.org/service/local/artifact/maven/redirect?r=${REPOSITORY}&g=org.mock-server&a=mockserver-netty&c=jar-with-dependencies&e=jar&v=${VERSION}
RUN wget --max-redirect=10 -O mockserver-netty-jar-with-dependencies.jar "$REPOSITORY_URL"

# runtime image
FROM gcr.io/distroless/java:11

# maintainer details
LABEL MAINTAINER.NAME="James Bloom" MAINTAINER.EMAIL="jamesdbloom@gmail.com"

# expose ports.
EXPOSE 1080

# copy in jar
COPY --from=build mockserver-netty-jar-with-dependencies.jar /

# don't run MockServer as root
USER nonroot

VOLUME [ "/config", "/libs", "/etc/localtime" ]

ENTRYPOINT ["java", "-Duser.timezone=Asia/Shanghai", "-Dfile.encoding=UTF-8", "-cp", "/mockserver-netty-jar-with-dependencies.jar:/libs/*", "-Dmockserver.propertyFile=/config/mockserver.properties", "org.mockserver.cli.Main"]

CMD ["-serverPort", "1080"]

使用示例

使用REST创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
PUT http://mockserver.zxytech.com:1080/mockserver/expectation HTTP/1.1
Content-Type: application/json

{
"httpRequest" : {
"path" : "/authentication/verify-id",
},
"httpResponse" : {
"body" : {
"code": "0000",
"message": "操作成功"
}
}
}

在rdm-automation中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Test(description = "云账户接口mock")
public void testMockYunzhanghuWxpay() {
new MockServerClient("mockserver.zxytech.com", 1080)
.clear(
request()
.withPath("/authentication/verify-id")
);

new MockServerClient("mockserver.zxytech.com", 1080)
.when(
request()
.withPath("/authentication/verify-id")
)
.respond(
HttpResponse.response()
.withBody(JsonBody.json("{\"code\":\"0000\",\"message\":\"操作成功\"}"))
);

new MockServerClient("mockserver.zxytech.com", 1080)
.clear(
request()
.withPath(MockOrderWxpayCallback.MOCK_URL_PATH)
);

new MockServerClient("mockserver.zxytech.com", 1080)
.when(
request()
.withPath(MockOrderWxpayCallback.MOCK_URL_PATH)
)
.respond(
HttpClassCallback.callback()
.withCallbackClass(MockOrderWxpayCallback.class)
);

}

MockOrderWxpayCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class MockOrderWxpayCallback implements ExpectationResponseCallback {

public static final String MOCK_URL_PATH = "/api/payment/v1/order-wxpay";

private static void asyncCallback(OrderResp orderResp) {
orderResp.setStatus(Status.PROCESSED.getCode());
orderResp.setStatusMessage(Status.PROCESSED.getDescribe());
orderResp.setFinishedTime(String.valueOf(System.currentTimeMillis()));

new Scheduler(new MockServerLogger()).schedule(() -> {
Map<String, Object> resp = new HashMap<>(6);
resp.put("mess", RandomStringUtils.randomNumeric(10));
resp.put("timestamp", System.currentTimeMillis() / 1000);
try {
resp.put("data", encrypt(JSONObject.toJSONString(orderResp)));
resp.put("sign", sign(resp));
} catch (Exception e) {
post("http://mockserver.zxytech.com:1080/withdraw/yunzhanghu/callback",
e.getMessage());
}
post(orderResp.getNotifyUrl(), JSONObject.toJSONString(resp));

}, false, Delay.seconds(1));
}

@Override
public HttpResponse handle(HttpRequest httpRequest) throws Exception {
String reqData = urldecode(httpRequest.getBodyAsString()).get("data").toString();
String reqStr = decrypt(reqData);

OrderResp orderResp = JSONObject.parseObject(reqStr, OrderResp.class);
orderResp.setRef(RandomStringUtils.randomNumeric(20));
orderResp.setStatusDetail(StatusDetail.SUCCEED.getCode());
orderResp.setStatusDetailMessage(StatusDetail.SUCCEED.getDescribe());

asyncCallback(SerializationUtils.clone(orderResp));

orderResp.setStatus(Status.APPLIED.getCode());
orderResp.setStatusMessage(Status.APPLIED.getDescribe());
Map<String, Object> resp = new HashMap<>(3);
resp.put("code", "0000");
resp.put("message", "操作成功");
resp.put("data", orderResp);
return HttpResponse.response().withBody(new JsonBody(JSONObject.toJSONString(resp)));
}

}

参考文档

https://www.mock-server.com/mock_server/creating_expectations.html

https://www.mock-server.com/mock_server/getting_started.html

https://www.mock-server.com/mock_server/running_mock_server.html#docker_container

https://www.mock-server.com/mock_server/configuration_properties.html

https://www.mock-server.com/#what-is-mockserver

https://www.mock-server.com/#why-use-mockserver

https://www.mock-server.com/mock_server/mockserver_clients.html

https://tech.meituan.com/2015/10/19/mock-server-in-action.html