I am trying to add end to end testing to our code base and we have one gRPC client in our code that calls a gRPC server for a server streaming call. In our testing infrastructure we have a mock HttpHandler that basically acts as a queue to respond to incoming requests with predetermined mock values and this works for all the REST APIs, so I am trying to see if I can take advantage of this infrastructure to help solve my issue as well.
What I am trying to do is make the httphandler respond with an encoded mock gRPC object so my client can decode it, but I am running into some problems. I am looking for either an idea on how I can do the encoding so it works or some other new way. Or if what I am trying to do is not possible that would be nice to know as well as I have already spent a fair amount of time on this problem.
What I am doing:
First, I create my gRPC client with the mock httphandler that we use for end to end testing like so:
string gRPCServerEndpoint = "http://localhost/";
var channelOptions = new GrpcChannelOptions();
var grcpWebHandler = new GrpcWebHandler(new GrpcSubdirectoryHandler(this.MockHttpMessageHandler, "/grpcresource"));
channelOptions.HttpHandler = grcpWebHandler;
var channel = GrpcChannel.ForAddress(new Uri(gRPCServerEndpoint), channelOptions);
SomeService.SomeServiceClient grpcClient = new SomeService.SomeServiceClient(channel.CreateCallInvoker());
Now I know instead of creating the actual client, I can use NSubstitue<> to mock the client and make the resource I am calling return an AsyncServerStreamingCall() object I need, but I don't want to mock the grpc client as I want this to be end to end testing and I already have the mocked version for my unit tests.
So after creating the client with the mock http handler, then I tell the mock http handler what it should respond to the incoming gRPC request to act as the server.
So far what I tired is turning the gRPC object that the proto file created into a bytes and try sending that as part of HttpResponseMessage() but it's not working.
I am doing it like so:
ProtoFileGeneratedCSharpObject cb = new ProtoFileGeneratedCSharpObject();
cb.ErrorMessage = "error";
byte[] stream = new byte[cb.CalculateSize()];
using (Google.Protobuf.CodedOutputStream outstream = new Google.Protobuf.CodedOutputStream(stream))
{
cb.WriteTo(outstream);
outstream.Flush();
}
var byteContent = new ByteArrayContent(stream);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/grpc");
var mockHttpResponse = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = byteContent
};
So now the mock http handler does respond with the mockHttpResponse when my grpc client makes a call. So that's all good.
Then my gRPC client makes the call like so:
using var call = this.someServiceClient.BatchGet(request, headers);
var tokenSource = new CancellationTokenSource();
while (await call.ResponseStream.MoveNext(tokenSource.Token))
{
ProtoFileGeneratedCSharpObject response = call.ResponseStream.Current;
responses.Add(response);
}
But the after receiving the response, the client throws an error saying:
{"Status(StatusCode=\"Internal\", Detail=\"Error reading next message. InvalidDataException: Unexpected compressed flag value in message header.\", DebugException=\"System.IO.InvalidDataException: Unexpected compressed flag value in message header.\")"}
So it seems like the header value "application/grpc" also gets compressed somehow. If I don't include the header, then the error is:
{"Status(StatusCode=\"Cancelled\", Detail=\"Bad gRPC response. Response did not have a content-type header.\")"}
I have also tried sending the response with System.Text.Encoding.UTF8.GetBytes(x); instead of cb.WriteTo(outstream); however that one had the same problem.
So my question is: is what I am trying to do possible? Can I encode my gRPC object in this way so it can be decoded by my client without spinning up a fake gRPC server? Basically by pass the gRPC server.
If so how can I achieve this?
I am not interested in spinning up a fake gRPC server because I want these tests to run alongside my unit tests on the same application.