GRPC protocol Mock Server service

Posted by php_jord on Fri, 21 Jan 2022 12:55:32 +0100

PowerMock is an implementation of Mock Server. It supports Mock of HTTP and gRPC protocol interfaces at the same time, and provides flexible plug-in functions. This tool is designed for developers with interface Mock requirements such as front and back end and testing. It can also be deployed in gateway architecture or API management platform as a general Mock service to realize functions such as degradation and interface Mock.

function

As a Mock Server, PowerMock has the following core functions:

  1. Mock that supports the # HTTP protocol # and # GRC protocol # interfaces.
  2. Support the configuration of scripting languages such as Javascript to dynamically generate responses.
  3. It supports configuring multiple responses to an interface and distinguishing them according to conditions.
  4. Matching criteria support multiple operators (and / or / > / < / = and so on).
  5. It supports the return of static data and random data in specific fields.
  6. Support plug-in} function, and other matching or Mock engines can be realized by writing plug-ins.
  7. At the same time, HTTP and gRPC interfaces are provided to dynamically add, delete, modify and query the MockAPI.
  8. Redis storage out of the box, and supports the free expansion of other storage engines, such as MySQL and etcd.
  9. Both 32-bit and 64 bit windows / darwin / linux are supported.
  10. Language independent, any project using HTTP protocol or gRPC protocol can use this tool.

Examples

1, More advanced usage

Take the following configuration as an example:

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

This configuration defines a MockAPI to match all paths to / examples greeter. api. Greeter / Hello, the method is "POST". It contains two scenarios, which can achieve such effects:

1. Condition scenario I

When uid < = 1000 in the request Header:

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

Where {{$mock.price}} is a magic variable used to return a random price data. Finally, the Response Body received by the client is similar to:

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

2. Condition scenario II

When the "uid > 1000" in the Header is requested, the response is returned by executing the following Javascript script:

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

In this script, the code, header, tracker and body of the response are generated according to the request header and some built-in or custom functions. The response body received by the final client is similar to:

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

It describes a relatively complex scenario. Of course, your needs may be relatively simple. In practice, let's start with Hello World!

2, Start with Hello World

First, create a configuration file:

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/

Put the compiled PowerMock and the configuration file created above into the same directory, as follows:

➜ 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

Then execute

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

If there is no port conflict, you should already be able to see the service running!

1. First Mock an HTTP interface

In the above directory, create a file named APIs Yaml files:

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!

Then run:

➜ ./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

In this way, the MockAPI we described is created.

Request via curl or your browser http://127.0.0.1:30003/hello , you can see the return to 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 another gRPC interface

In the above directory, create an apis directory to make the entire directory structure look like the following:

➜  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

Create our greeter in the apis directory 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;
}

Now the whole directory structure looks like this:

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

Rerun our powermock to load our newly written proto file:

➜ ./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

In the startup log, we can see that the newly created proto file has been loaded into PowerMock.

Put our APIs Modify the yaml file to the following:

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

You can see that a MockAPI named "hello_example_gRPC" is added. We load it through the following command:

➜ 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

In this way, our MockAPI is added to PowerMock.

If you have tools such as BloomRPC in your environment, you can load greeter through BloomRPC first Proto, and then call 127.0.0.1:30002:

You can see that the call successfully returns "hello world". If the programming language is used for calling, take golang as an example and call PowerMock through the following code:

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

The log output is as follows:

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"

As you can see, our interface was successfully mocked out!

install

Install via Go

Install the normal version without Javascript support:

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

Install V8 version and support Javascript:

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

Out of the box version

If you don't have the need to customize the plug-in, the out of the box version is very suitable for you (if necessary, you can add me QQ3177181324).

Compile through Makefile

If you are a linux/darwin/wsl user, it is recommended to use makefile for installation:

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

Of course, you can also compile directly:

➜ cd ./cmd/powermock
➜ go install
➜ go build .

last:

1. Like, collect. In case you can't find it in the future, you can find it on your home page when you want to see it. It's very convenient;

2. Follow me. Let's become a long-term relationship. The next content will share more hard core dry goods;

3. Article learning resources can be shared free of charge. Add group 323432957 required.

Don't just be a person who never stops collecting and never starts action. There are many things that you can't learn from yourself. If you can think a little more and look at other people's experiences and practices, you will grow faster and have better results! Come on, tester! The road is at your feet, and success is tomorrow!

I am a Xingjun. I output valuable content with my heart. If you are in full bloom, the breeze comes from me!

It's not easy to create. I don't want to be prostituted. Your "praise" is the biggest driving force for a Xingjun's creation. See you in the next article!
 

Topics: Python Java Linux software testing Testing