simple gRPC testing with karate and a dynamic client using polyglot.
karate-grpc makes it easy to:
- build complex protobuf request payloads via json
- traverse data within the responses
- chain data from responses into the next request.
Requires maven to be installed
$ # compile and test the whole project
$ mvn clean install
$ # test demo
$ cd karate-grpc-demo
$ mvn test
$ # or run single test
$ mvn test -Dtest=HelloWorldNewRunner
When running tests, the hello world grpc server is started/stopped automatically in AbstractTestBase.java
.
Karate also generates beautiful test reports:
A set of real-life examples which includes single rpc
, client stream rpc
, server stream rpc
and bidi stream rpc
can be found here: karate-grpc-demo
karate-grpc requires Java 8 and then Maven to be installed, these also are required by karate and polyglot.
karate-grpc only support Maven currently.
You need to add the following <dependencies>
:
<dependency>
<groupId>com.github.thinkerou</groupId>
<artifactId>karate-grpc-core</artifactId>
<version>1.0.7</version>
</dependency>
TODO: need to test!!!
Alternatively for Gradle you need to add the following entry:
testImplementation 'com.github.thinkerou:karate-grpc-core:1.0.7'
And simulates karate-grpc-helper
and karate-grpc-demo
build your redis helper project and test project.
Testing one grpc server, we have the follow info:
-
grpc server
ip
andport
. -
(optional) protobuf file corresponding grpc server, but usually it's protobuf
jar package
not one single file or more files.
So, we could test it based on the two point.
For testing your grpc server, as above, need protobuf jar dependency and protobuf build plugins - protobuf-maven-plugin
.
MUST appoint descriptorSetFileName
and protoSourceRoot
params:
<descriptorSetFileName>karate-grpc.protobin</descriptorSetFileName>
<protoSourceRoot>${project.build.directory}/dependency/demo</protoSourceRoot>
Especially, descriptorSetFileName
MUST equal karate-grpc.protobin
, please see here about more details.
And other pom settings are the same as karate
.
We need to use Java interop of Karate in order to call us define grpc client.
And use JSON.parse
javascript function parse the response of grpc server return value.
So, use karate-grpc
need the following steps:
- Calls into karate-grpc GrpcClient via Java Interop.
* def GrpcClient = Java.type('com.github.thinkerou.karate.GrpcClient')
- Builds one public Grpc client using your grpc ip and port.
* def client = new GrpcClient('localhost', 50051)
If you want to list protobuf by service name or/and message name, you should use:
* def client = new GrpcClient()
Because you don't need grpc server ip/port when listing protobuf.
-
Reads JSON data corresponding your protobuf definition.
-
Calls your Grpc server using
call
of karate-grpc.
* def response = client.call('helloworld.Greeter/SayHello', payload, karate)
call
has two required params, and one optional
- protobuf full name(
format:<package-name>.<service-name>/<rpc-name>
) - JSON data.
karate
(optional, nullable) -- if present, will add[request]
and[response]
to html report.karate
variable is of type ScenarioBridge and is automatically created when running karate tests.
If you input protobuf full name error, call
will fail and output protobuf message by list
, like this:
When input helloworld.Greeter/SayHello1
, it will fail and print log:
Oct 11, 2018 6:53:24 PM com.github.thinkerou.karate.service.GrpcCall invoke
警告: Call grpc failed, maybe you should see the follow grpc information.
Oct 11, 2018 6:53:24 PM com.github.thinkerou.karate.service.GrpcCall invoke
信息: [
{
"helloworld.Greeter/SayHelloBiStreaming":"",
"helloworld.Greeter/RecordRoute":"",
"helloworld.Greeter/RouteChat":"",
"helloworld.Greeter/SayHelloServerStreaming":"",
"helloworld.Greeter/ListFeatures":"",
"helloworld.Greeter/SayHello":"",
"helloworld.Greeter/AgainSayHello":"",
"helloworld.Greeter/SayHelloClientStreaming":"",
"helloworld.Greeter/GetFeature":""
}
]
- Converts response string to JSON.
* def response = JSON.parse(response)
Because call
of karate-grpc returns JSON string, we need to convert it and then can use match
assertion.
-
Asserts payload.
-
(Optional) Saves response for second Grpc.
If have second Grpc use the response of first Grpc, we should save it, like:
* def message = response[0].message
And use it on JSON file:
[
{
"message": "#(message)",
"address": "BeiJing"
}
]
- (Optional) Second Grpc call using response data before.
One whole example likes this:
Feature: grpc helloworld example by grpc dynamic client
Background:
* def client = Java.type('demo.DemoGrpcClientSingleton').INSTANCE.getGrpcClient();
Scenario: do it
* string payload = read('helloworld.json')
* def response = client.call('helloworld.Greeter/SayHello', payload)
* def response = JSON.parse(response)
* print response
* match response[0].message == 'Hello thinkerou'
* def message = response[0].message
* string payload = read('again-helloworld.json')
* def response = client.call('helloworld.Greeter/AgainSayHello', payload)
* def response = JSON.parse(response)
* match response[0].details == 'Details Hello thinkerou in BeiJing'
Because karate-grpc
supports stream grpc, we use list
JSON.
Input JSON file like:
[
{
"name": "thinkerou"
},
{
"name": "thinkerou2"
}
]
Output JSON string also like:
[
{
"message": "Hello thinkerou"
},
{
"message": "Hello thinkerou2"
}
]
That's all!!!
Using redis is optional, but caching descriptor sets may save compile time, especially when your project has many protobuf jar package dependencies.
You can even use jedis-mock so you don't even need to install Redis. see MockRedisHelperSingleton.java:
Note: while the redis test implementation is thread-safe, Redis uses single-threaded execution so test performance may be degraded for high concurrency.
To use redis, use class com.github.thinkerou.karate.RedisGrpcClient
instead of com.github.thinkerou.karate.GrpClient
public enum DemoGrpcClientSingleton {
INSTANCE;
RedisGrpcClient redisGrpcClient;
public GrpcClient getGrpcClient() {
return redisGrpcClient;
}
DemoGrpcClientSingleton() {
redisGrpcClient = new RedisGrpcClient("localhost", 50051, MockRedisHelperSingleton.INSTANCE.getRedisHelper());
}
}
TODO:
- Save
ProtoFullName|InputType|InputMessage|OutputType|OutputMessage|ProtoFileName|RPCAddress
not file content. - Support java reflection mode.
Note:
- The part content is outdated draft which initially think about the topic which continues to have saved is for reference only.
- Usually you no need to care it and skip it, because
karate-grpc-core
have completed the function.
You only need two steps:
-
Read json file and parse protobuf object to the request of grpc server
-
format the response of grpc server to json string and return it as grpc server
Like this:
public class Client {
// ...
public static String greet(String input) {
HelloRequest.Builder requestBuilder = HelloRequest.newBuilder();
try {
JsonFormat.parser().merge(input, requestBuilder);
} catch (ProtocolBufferException e) {
// ...
}
HelloReply response = null;
try {
response = blockingStub.sayHello(requestBuilder.build());
} catch (StatusRuntimeException e) {
// ...
}
String res = "";
try {
res = JsonFormat.printer().print(response);
} catch (ProtocolBufferException e) {
// ...
}
return res;
}
// ...
}
Thanks Peter Thomas for his work for karate and his generous help, also thanks Dino Wernli for his contributions for polyglot. And the favicon of organization generate at favicon.io.
Maybe you want to know more information about Karate or other, please read the follow contents:
-
karate project home: https://github.com/intuit/karate
-
polyglot project home: https://github.com/grpc-ecosystem/polyglot
-
ProtoBuf(via JSON) project home: https://github.com/protocolbuffers/protobuf/tree/master/java
-
grpc-java project home: https://github.com/grpc/grpc-java
karate-grpc is licensed under MIT License.