PowerMock 支持gRPC/HTTP协议,功能丰富的Mock Server实现

power

PowerMock是一个Mock Server的实现,它同时支持HTTP与gRPC协议接口的Mock,并提供了灵活的插件功能。
这个工具面向于前后端、测试等对有接口Mock需求的开发人员,也可以作为一个通用的Mock服务,部署在网关架构或API管理平台中,实现降级、接口Mock等功能。

项目地址

项目地址:PowerMock

功能

作为一个Mock Server,PowerMock具有以下的核心功能:

  1. 支持 HTTP协议gRPC协议 接口的Mock。
  2. 支持配置 Javascript 等脚本语言来动态生成响应。
  3. 支持对一个接口配置多种响应,并按照条件进行区分。
  4. 匹配条件支持多种运算符(AND/OR/>/</=等)。
  5. 支持返回静态数据以及 特定领域的随机数据
  6. 支持 插件 功能,可以通过编写插件实现其他匹配或Mock引擎。
  7. 同时提供HTTP与gRPC接口,可以动态对MockAPI进行 增删改查
  8. 开箱即用的Redis存储,并支持自由拓展其他存储引擎,比如MySQL、etcd。
  9. 同时支持 windows / darwin / linux 的 32 位 与 64 位。
  10. 语言无关,任何使用HTTP协议或gRPC协议的项目均可以使用本工具。

示例

一、较为高级的用法

本示例可以在 示例代码 找到对应资料
本示例必须使用v8版本的powermock,才能完整支持Javascript的功能

以下面这份配置为示例:

uniqueKey: "advanced_example"
path: "/examples.greeter.api.Greeter/Hello"
method: "POST"
cases:
  - condition:
      simple:
        items:
          - operandX: "$request.header.uid"
            operator: "<="
            operandY: "1000"
    response:
      simple:
        header:
          x-unit-id: "3"
          x-unit-region: "sh"
        trailer:
          x-api-version: "1.3.2"
        body: |
          {"timestamp": "1111", "message": "This message will only be returned when uid <= 1000", "amount": "{{ $mock.price }}"}
  - condition:
      simple:
        items:
          - operandX: "$request.header.uid"
            operator: ">"
            operandY: "1000"
    response:
      script:
        lang: "javascript"
        content: |
          (function(){
              function random(min, max){
                  return parseInt(Math.random()*(max-min+1)+min,10);
              }
              return {
                  code: 0,
                  header: {
                      "x-unit-id": (request.header["uid"] % 5).toString(),
                      "x-unit-region": "bj",
                  },
                  trailer: {
                      "x-api-version": "1.3.2",
                  },
                  body: {
                      timestamp: Math.ceil(new Date().getTime() / 1000),
                      message: "this message is generated by javascript, your uid is: " + request.header["uid"],
                      amount: random(0, 5000),
                  },
              }
          })()

这份配置定义了一个MockAPI,用于匹配所有路径为 /examples.greeter.api.Greeter/Hello,方法为 POST 的请求,它包含了两个场景,能够实现这样的效果:

1. 条件场景一

当请求 Header 中的 uid <= 1000 时:

  • Response Header 中写入:
x-unit-id: "3"
x-unit-region: "sh"
  • Response Trailer 中写入:
x-api-version: "1.3.2"
  • Response Body 中写入:
{"timestamp": "1111", "message": "This message will only be returned when uid <= 1000", "amount": "{{ $mock.price }}"}

其中的 {{ $mock.price }} 是魔法变量,用于返回一个随机的价格数据。最终,客户端收到的 Response Body 类似于:

{
    "timestamp": "1111",
    "message": "This message will only be returned when uid <= 1000",
    "amount": 7308.4
}

2. 条件场景二

当请求 Header 中的 uid > 1000 时,通过执行以下Javascript脚本返回响应:

(function(){
    function random(min, max){
        return parseInt(Math.random()*(max-min+1)+min,10);
    }
    return {
        code: 0,
        header: {
            "x-unit-id": (request.header["uid"] % 5).toString(),
            "x-unit-region": "bj",
        },
        trailer: {
            "x-api-version": "1.3.2",
        },
        body: {
            timestamp: Math.ceil(new Date().getTime() / 1000),
            message: "this message is generated by javascript, your uid is: " + request.header["uid"],
            amount: random(0, 5000),
        },
    }
})()

在这个脚本中,根据请求的 Header,以及一些内置或自定义函数来生成了响应的code、header、trailer与body。
最终客户端收到的响应体类似于:

{
    "timestamp": 1622093545,
    "message": "this message is generated by javascript, your uid is: 2233",
    "amount": 314
}

它描述了一个相对复杂的场景,当然可能你的需求比较简单,实战的话,我们先从Hello World开始吧!

二、从Hello World开始吧

本示例可以在 示例代码 找到对应资料

首先,创建一个配置文件:

log:
    pretty: true
    level: debug
grpcmockserver:
    enable: true
    address: 0.0.0.0:30002
    protomanager:
        protoimportpaths: [ ]
        protodir: ./apis
httpmockserver:
    enable: true
    address: 0.0.0.0:30003
apimanager:
    grpcaddress: 0.0.0.0:30000
    httpaddress: 0.0.0.0:30001
pluginregistry: { }
plugin:
    simple: { }
    grpc: { }
    http: { }
    script: { }
    redis:
        enable: false
        addr: 127.0.0.1:6379
        password: ""
        db: 0
        prefix: /powermock/

将编译好的PowerMock与上面创建好的配置文件放到同一个目录中,像下面这样:

➜ ls -alh
total 45M
drwxrwxrwx 1 storyicon storyicon 4.0K May 27 14:18 .
drwxrwxrwx 1 storyicon storyicon 4.0K May 24 11:43 ..
-rwxrwxrwx 1 storyicon storyicon  546 May 27 14:16 config.yaml
-rwxrwxrwx 1 storyicon storyicon  45M May 27 14:18 powermock

然后执行

➜ ./powermock serve --config.file config.yaml

如果没有端口冲突的话,你应该已经可以看到服务运行起来了!

1. 先Mock一个HTTP接口

在上面的目录下,创建一个名为 apis.yaml 的文件:

uniqueKey: "hello_example_http"
path: "/hello"
method: "GET"
cases:
    - response:
          simple:
              header:
                  x-unit-id: "3"
                  x-unit-region: "sh"
              trailer:
                  x-api-version: "1.3.2"
              body: |
                  hello world!

然后运行:

➜ ./powermock load --address=127.0.0.1:30000 apis.yaml
2:32PM INF start to load file component=main file=load.go:59
2:32PM INF mock apis loaded from file component=main count=1 file=load.go:64
2:32PM INF start to save api component=main file=load.go:76 host= method=GET path=/hello uniqueKey=hello
2:32PM INF succeed! component=main file=load.go:89

这样,我们描述的MockAPI就创建起来了。

通过 curl 或者你的浏览器请求 http://127.0.0.1:30003/hello,可以看到返回给我们 hello world 了!

➜ curl http://127.0.0.1:30003/hello -i
HTTP/1.1 200 OK
Content-Type: application/json
X-Unit-Id: 3
X-Unit-Region: sh
Date: Thu, 27 May 2021 06:36:28 GMT
Content-Length: 12

hello world!

2. 再mock一个gRPC接口

在上面的目录中,创建一个 apis 目录,使整个目录结构像下面这样:

➜  ls -alh
total 45M
drwxrwxrwx 1 storyicon storyicon 4.0K May 27 14:42 .
drwxrwxrwx 1 storyicon storyicon 4.0K May 27 14:37 ..
drwxrwxrwx 1 storyicon storyicon 4.0K May 27 14:23 apis
-rwxrwxrwx 1 storyicon storyicon 1.8K May 27 14:32 apis.yaml
-rwxrwxrwx 1 storyicon storyicon  546 May 27 14:16 config.yaml
-rwxrwxrwx 1 storyicon storyicon  45M May 27 14:18 powermock

在 apis 目录中创建我们的 greeter.proto:

syntax = "proto3";

package examples.greeter.api;
option go_package = "github.com/bilibili-base/powermock/examples/helloWorld/apis;apis";

service Greeter {
    rpc Hello(HelloRequest) returns (HelloResponse);
}

message HelloRequest {
    string message = 2;
}

message HelloResponse {
    string message = 2;
}

现在整个目录结构像这样:

.
├── apis
│   └── greeter.proto
├── apis.yaml
├── config.yaml
└── powermock

重新运行我们的 powermock 来加载我们新写的proto文件:

➜ ./powermock serve --config.file config.yaml
2:55PM INF starting load proto from: ./apis component=main.gRPCMockServer.protoManager file=service.go:102
2:55PM INF api loaded component=main.gRPCMockServer.protoManager file=service.go:131 name=/examples.greeter.api.Greeter/Hello

在启动日志中可以看到我们新创建的 proto 文件已经被加载到 PowerMock 中了。

将我们的 apis.yaml 文件修改成下面的内容:

uniqueKey: "hello_example_http"
path: "/hello"
method: "GET"
cases:
    - response:
          simple:
              header:
                  x-unit-id: "3"
                  x-unit-region: "sh"
              trailer:
                  x-api-version: "1.3.2"
              body: |
                  hello world!

---

uniqueKey: "hello_example_gRPC"
path: "/examples.greeter.api.Greeter/Hello"
method: "POST"
cases:
    - response:
          simple:
              header:
                  x-unit-id: "3"
                  x-unit-region: "sh"
              trailer:
                  x-api-version: "1.3.2"
              body: |
                  {"message": "hello world!"}

可以看到,里面添加了一个名为 "hello_example_gRPC" 的 MockAPI,我们通过下面的命令装载它:

➜ powermock load --address=127.0.0.1:30000  apis.yaml
3:06PM INF start to load file component=main file=load.go:59
3:06PM INF mock apis loaded from file component=main count=2 file=load.go:64
3:06PM INF start to save api component=main file=load.go:76 host= method=GET path=/hello uniqueKey=hello_example_http
3:06PM INF start to save api component=main file=load.go:76 host= method=POST path=/examples.greeter.api.Greeter/Hello uniqueKey=hello_example_gRPC
3:06PM INF succeed! component=main file=load.go:89

这样,我们的MockAPI就被添加到PowerMock中了。

如果你的环境中有BloomRPC之类的工具的话,可以先通过BloomRPC加载 greeter.proto,然后调用 127.0.0.1:30002

[图片上传失败...(image-abf054-1625472916018)]

可以看到,调用成功返回了 "hello world"。
如果使用编程语言进行调用的话,以 golang 为例,通过下面的代码调用 PowerMock:

func main() {
    fmt.Println("starting call mock server")
    conn, err := grpc.Dial("127.0.0.1:30002", grpc.WithInsecure())
    if err != nil {
        panic(err)
    }
    client := apis.NewGreeterClient(conn)

    var header, trailer metadata.MD
    startTime := time.Now()
    resp, err := client.Hello(context.TODO(), &apis.HelloRequest{
        Message: "hi",
    }, grpc.Header(&header), grpc.Trailer(&trailer))
    if err != nil {
        panic(err)
    }
    fmt.Printf("[elapsed] %d ms \r\n", time.Since(startTime).Milliseconds())
    fmt.Printf("[headers] %+v \r\n", header)
    fmt.Printf("[trailer] %+v \r\n", trailer)
    fmt.Printf("[response] %+v \r\n", resp.String())
}

日志输出是这样的:

starting call mock server
[elapsed] 2 ms
[headers] map[content-type:[application/grpc] x-unit-id:[3] x-unit-region:[sh]]
[trailer] map[x-api-version:[1.3.2]]
[response] message:"This message will only be returned when uid <= 1000"

可以看到,我们的接口被成功Mock出来了!

安装

通过Go安装

安装普通版本,无Javascript支持:

go install github.com/bilibili-base/powermock/cmd/powermock

安装V8版本,支持Javascript:

go install github.com/bilibili-base/powermock/cmd/powermock-v8

开箱即用版本

如果你没有定制插件的需求,开箱即用版本 非常适合你。

通过Makefile编译

如果你是 linux/darwin/wsl 的用户,推荐使用 makefile 来进行安装:

➜ git clone https://github.com/bilibili-base/powermock
➜ cd powermock
➜ make build_linux_v8
➜ make build_linux
➜ make build_darwin
➜ make build_windows

当然也可以直接进行编译:

➜ cd ./cmd/powermock
➜ go install
➜ go build .
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349