?PowerMock是一個Mock Server的實現(xiàn),它同時支持HTTP與gRPC協(xié)議接口的Mock,并提供了靈活的插件功能。 這個工具面向于前后端、測試等對有接口Mock需求的開發(fā)人員,也可以作為一個通用的Mock服務,部署在網(wǎng)關架構或API管理平臺中,實現(xiàn)降級、接口Mock等功能。
功能
作為一個Mock Server,PowerMock具有以下的核心功能:
支持HTTP協(xié)議與gRPC協(xié)議接口的Mock。
支持配置Javascript等腳本語言來動態(tài)生成響應。
支持對一個接口配置多種響應,并按照條件進行區(qū)分。
匹配條件支持多種運算符(AND/OR/>/
支持返回靜態(tài)數(shù)據(jù)以及特定領域的隨機數(shù)據(jù)。
支持插件功能,可以通過編寫插件實現(xiàn)其他匹配或Mock引擎。
同時提供HTTP與gRPC接口,可以動態(tài)對MockAPI進行增刪改查。
開箱即用的Redis存儲,并支持自由拓展其他存儲引擎,比如MySQL、etcd。
同時支持 windows / darwin / linux 的 32 位 與 64 位。
語言無關,任何使用HTTP協(xié)議或gRPC協(xié)議的項目均可以使用本工具。
示例
一、較為高級的用法
以下面這份配置為示例:
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的請求,它包含了兩個場景,能夠實現(xiàn)這樣的效果:
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 }}是魔法變量,用于返回一個隨機的價格數(shù)據(jù)。最終,客戶端收到的Response Body類似于:
{
"timestamp": "1111",
"message": "This message will only be returned when uid <= 1000",
"amount": 7308.4
}
2. 條件場景二
當請求 Header 中的uid > 1000時,通過執(zhí)行以下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),
? ? ? ? },
? ? }
})()
在這個腳本中,根據(jù)請求的 Header,以及一些內置或自定義函數(shù)來生成了響應的code、header、trailer與body。 最終客戶端收到的響應體類似于:
{
"timestamp": 1622093545,
"message": "this message is generated by javascript, your uid is: 2233",
"amount": 314
}
它描述了一個相對復雜的場景,當然可能你的需求比較簡單,實戰(zhàn)的話,我們先從Hello World開始吧!
二、從Hello World開始吧
首先,創(chuàng)建一個配置文件:
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與上面創(chuàng)建好的配置文件放到同一個目錄中,像下面這樣:
? 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
然后執(zhí)行
? ./powermock serve --config.file config.yaml
如果沒有端口沖突的話,你應該已經(jīng)可以看到服務運行起來了!
1. 先Mock一個HTTP接口
在上面的目錄下,創(chuàng)建一個名為 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就創(chuàng)建起來了。
通過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接口
在上面的目錄中,創(chuàng)建一個 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 目錄中創(chuàng)建我們的 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;
}
現(xiàn)在整個目錄結構像這樣:
.
├── 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
在啟動日志中可以看到我們新創(chuàng)建的 proto 文件已經(jīng)被加載到 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中了。
如果你的環(huán)境中有BloomRPC之類的工具的話,可以先通過BloomRPC加載 greeter.proto,然后調用127.0.0.1:30002:
可以看到,調用成功返回了 "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
開箱即用版本
如果你沒有定制插件的需求,?開箱即用版本非常適合你(有需要的可以加我QQ3177181324)。
通過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 .
最后:
1、點贊,收藏。防止以后找不到,想看的時候,在自己主頁就能找到了,很方便;
2、關注我。讓我們成為長期關系,下一個內容會分享更多的硬核干貨;
3、文章學習資源,均可以免費分享。需要的加群323432957。
不要只做收藏從未停止,行動從未開始的人,很多事情,做著做著就無師自通了。如果在做的過程中還能稍微加點思考,稍微看一些別人的經(jīng)驗和做法,成長會更快,效果也會更好!加油吧,測試人!路就在腳下,成功就在明天!
我是阿星君,用心輸出有價值的內容,你若盛開,清風自來!
創(chuàng)作不易,不想被白嫖,各位的「點贊」就是阿星君創(chuàng)作的最大動力,我們下篇文章見!