GRPC協(xié)議 Mock Server服務(wù)

?PowerMock是一個(gè)Mock Server的實(shí)現(xiàn)使套,它同時(shí)支持HTTP與gRPC協(xié)議接口的Mock,并提供了靈活的插件功能。 這個(gè)工具面向于前后端、測試等對有接口Mock需求的開發(fā)人員乾巧,也可以作為一個(gè)通用的Mock服務(wù),部署在網(wǎng)關(guān)架構(gòu)或API管理平臺中僵闯,實(shí)現(xiàn)降級卧抗、接口Mock等功能藤滥。

功能

作為一個(gè)Mock Server鳖粟,PowerMock具有以下的核心功能:

支持HTTP協(xié)議gRPC協(xié)議接口的Mock。

支持配置Javascript等腳本語言來動態(tài)生成響應(yīng)拙绊。

支持對一個(gè)接口配置多種響應(yīng)向图,并按照條件進(jìn)行區(qū)分。

匹配條件支持多種運(yùn)算符(AND/OR/>/

支持返回靜態(tài)數(shù)據(jù)以及特定領(lǐng)域的隨機(jī)數(shù)據(jù)标沪。

支持插件功能榄攀,可以通過編寫插件實(shí)現(xiàn)其他匹配或Mock引擎。

同時(shí)提供HTTP與gRPC接口金句,可以動態(tài)對MockAPI進(jìn)行增刪改查檩赢。

開箱即用的Redis存儲,并支持自由拓展其他存儲引擎违寞,比如MySQL贞瞒、etcd。

同時(shí)支持 windows / darwin / linux 的 32 位 與 64 位趁曼。

語言無關(guān)军浆,任何使用HTTP協(xié)議或gRPC協(xié)議的項(xiàng)目均可以使用本工具。

示例

一挡闰、較為高級的用法

以下面這份配置為示例:

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),

? ? ? ? ? ? ? ? ? },

? ? ? ? ? ? ? }

? ? ? ? ? })()

這份配置定義了一個(gè)MockAPI乒融,用于匹配所有路徑為/examples.greeter.api.Greeter/Hello掰盘,方法為POST的請求,它包含了兩個(gè)場景赞季,能夠?qū)崿F(xiàn)這樣的效果:

1. 條件場景一

當(dāng)請求 Header 中的uid <= 1000時(shí):

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 }}是魔法變量愧捕,用于返回一個(gè)隨機(jī)的價(jià)格數(shù)據(jù)。最終申钩,客戶端收到的Response Body類似于:

{

"timestamp": "1111",

"message": "This message will only be returned when uid <= 1000",

"amount": 7308.4

}

2. 條件場景二

當(dāng)請求 Header 中的uid > 1000時(shí)晃财,通過執(zhí)行以下Javascript腳本返回響應(yīng):

(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),

? ? ? ? },

? ? }

})()

在這個(gè)腳本中,根據(jù)請求的 Header典蜕,以及一些內(nèi)置或自定義函數(shù)來生成了響應(yīng)的code断盛、header、trailer與body愉舔。 最終客戶端收到的響應(yīng)體類似于:

{

"timestamp": 1622093545,

"message": "this message is generated by javascript, your uid is: 2233",

"amount": 314

}

它描述了一個(gè)相對復(fù)雜的場景钢猛,當(dāng)然可能你的需求比較簡單,實(shí)戰(zhàn)的話轩缤,我們先從Hello World開始吧命迈!

二、從Hello World開始吧

首先火的,創(chuàng)建一個(gè)配置文件:

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)建好的配置文件放到同一個(gè)目錄中壶愤,像下面這樣:

? 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

如果沒有端口沖突的話,你應(yīng)該已經(jīng)可以看到服務(wù)運(yùn)行起來了!

1. 先Mock一個(gè)HTTP接口

在上面的目錄下馏鹤,創(chuàng)建一個(gè)名為 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!

然后運(yùn)行:

? ./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一個(gè)gRPC接口

在上面的目錄中,創(chuàng)建一個(gè) apis 目錄治力,使整個(gè)目錄結(jié)構(gòu)像下面這樣:

?? 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)在整個(gè)目錄結(jié)構(gòu)像這樣:

.

├── apis

│?? └── greeter.proto

├── apis.yaml

├── config.yaml

└── powermock

重新運(yùn)行我們的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 文件修改成下面的內(nèi)容:

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!"}

可以看到,里面添加了一個(gè)名為 "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瓢省,然后調(diào)用127.0.0.1:30002:

可以看到,調(diào)用成功返回了 "hello world"箭券。 如果使用編程語言進(jìn)行調(diào)用的話净捅,以 golang 為例,通過下面的代碼調(diào)用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 來進(jìn)行安裝:

? git clone https://github.com/bilibili-base/powermock

? cd powermock

? make build_linux_v8

? make build_linux

? make build_darwin

? make build_windows

當(dāng)然也可以直接進(jìn)行編譯:

? cd ./cmd/powermock

? go install

? go build .

最后:

1液兽、點(diǎn)贊骂删,收藏。防止以后找不到四啰,想看的時(shí)候宁玫,在自己主頁就能找到了,很方便;

2柑晒、關(guān)注我欧瘪。讓我們成為長期關(guān)系,下一個(gè)內(nèi)容會分享更多的硬核干貨;

3匙赞、文章學(xué)習(xí)資源佛掖,均可以免費(fèi)分享。需要的加群323432957涌庭。

不要只做收藏從未停止芥被,行動從未開始的人,很多事情坐榆,做著做著就無師自通了拴魄。如果在做的過程中還能稍微加點(diǎn)思考,稍微看一些別人的經(jīng)驗(yàn)和做法猛拴,成長會更快羹铅,效果也會更好蚀狰!加油吧愉昆,測試人!路就在腳下麻蹋,成功就在明天跛溉!

我是阿星君,用心輸出有價(jià)值的內(nèi)容扮授,你若盛開芳室,清風(fēng)自來!

創(chuàng)作不易刹勃,不想被白嫖堪侯,各位的「點(diǎn)贊」就是阿星君創(chuàng)作的最大動力,我們下篇文章見荔仁!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伍宦,一起剝皮案震驚了整個(gè)濱河市芽死,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌次洼,老刑警劉巖关贵,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異卖毁,居然都是意外死亡揖曾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門亥啦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炭剪,“玉大人,你說我怎么就攤上這事翔脱∧罴溃” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵碍侦,是天一觀的道長粱坤。 經(jīng)常有香客問我,道長瓷产,這世上最難降的妖魔是什么站玄? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮濒旦,結(jié)果婚禮上株旷,老公的妹妹穿的比我還像新娘。我一直安慰自己尔邓,他們只是感情好晾剖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梯嗽,像睡著了一般齿尽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上灯节,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天循头,我揣著相機(jī)與錄音,去河邊找鬼炎疆。 笑死卡骂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的形入。 我是一名探鬼主播全跨,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼亿遂!你這毒婦竟也來了浓若?” 一聲冷哼從身側(cè)響起盒使,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎七嫌,沒想到半個(gè)月后少办,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诵原,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年英妓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绍赛。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蔓纠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吗蚌,到底是詐尸還是另有隱情腿倚,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布蚯妇,位于F島的核電站敷燎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏箩言。R本人自食惡果不足惜硬贯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陨收。 院中可真熱鬧饭豹,春花似錦、人聲如沸务漩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽饵骨。三九已至翘悉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宏悦,已是汗流浹背镐确。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留饼煞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓诗越,卻偏偏與公主長得像砖瞧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子嚷狞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容