Home

Awesome

GRPC-Mock-Server

Super fast, platform independent, standalone component for mocking GRPC services using WireMock.NET stubbing engine

Supported GRPC communication patterns

PatternImplementation status
request-reply
server-streaming
client-streaming
duplex-streaming

How does it work

GRPC-Mock-Server works in the following way:

sequenceDiagram    
    participant Tests
    participant TestedApp as Tested App
    box  GRPC-Mock-Server
    participant Frontend as Fronted [GRPC to HTTP proxy]
    participant Backend as Backend  [WireMock]
    end
    autonumber
    Tests->> Backend : Prepare mapping
    Tests->> TestedApp: call
    activate TestedApp
    TestedApp->>Frontend: Call GRPC service    
    activate Frontend
    Frontend ->> Frontend: Translate GRPC to HTTP
    Frontend ->>  Backend: Forward HTTP request to WireMock
    activate Backend
    Backend -->> Frontend: Respond with matched HTP request
    deactivate Backend
     Frontend ->> Frontend: Translate HTTP to GRPC
    Frontend -->> TestedApp : Return GRPC response
    deactivate Frontend
    deactivate TestedApp

How to run GRPC-Mock-Server

Option 1: Running docker container manually

docker run -it -p 5033:5033 -p 9095:9095 -v $(pwd)/protos:/protos cezarypiatek/grpc-mock-server

Ports:

Option 2: Using TestContainerGrpcMockServerConnector

TestContainerGrpcMockServerConnector uses Testcontainers for .NET to spin docker container directly from the C# code. This options requires docker service running locally.

await using var connector = new TestContainerGrpcMockServerConnector(protoDirectory: "protos", grpcPort:5033);

await connector.Install();

Option 3: Using TestChartGrpcMockServerConnector

TestChartGrpcMockServerConnector uses SmoothSailing to deploy GRPC-Mock-Server into Kubernetes cluster directly from the C# code. This option requires Helm and kubectl to be installed on the host machine.

stateDiagram-v2

    state "K8s Installation" as g1
    state "Pod startup" as g2
    state "Runtime" as g3
    state "Converts proto files to ConfigMap" as s1
    state "Installs Helm Chart with GRPC-Mock-Server" as s2
    state "Mounts ConfigMap with protos as storage to pod" as s22
    state "Compiles proto files" as s3
    state "Generates GRPC-To-HTTP proxy" as s4
    state "Compiles generated code" as s5
    state "Starts host app with GRPC-To-HTTP proxy and WireMock" as s6
    state "Handles GRPC calls" as s7
    state "Handles WireMock calls" as s8
     direction LR
    g1 --> g2
    g2 --> g3
    state g1
    {
        direction TB
        s1 --> s2
        s2 --> s22
    }
    state g2 {
        direction TB       
        s3 --> s4        
        s4 --> s5
        s5 --> s6
    }
    state g3{
        direction TB
        s7
        s8 
    }
    
var settings = new TestChartGrpcMockServerConnectorSettings
{
    ProtoDirectory = "protos",
    GrpcPort = 8889,
    ExposeStubbingPortOnLocalhost = true
};
await using var connector = new TestChartGrpcMockServerConnector(settings);

var connectionInfo = await connector.Install();

NuGet

All C# components required for Option 2 and Option 3 are provided by GrpcTestKit nuget package .

dotnet add package GrpcTestKit

Option 4: Using source generator + proto files

  1. Add the following nuget package references
<ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.53.0" />
    <PackageReference Include="WireMock.Net" Version="1.5.25" />
    <PackageReference Include="GrpcTestKit" Version="1.18.0" />
</ItemGroup>
  1. Include your proto files
<ItemGroup>
    <Protobuf Include="protos\**\*.proto" ProtoRoot="protos" GrpcServices="Server" />
</ItemGroup>
  1. Define partial class for your mock server
[GrpcMockServerForAutoDiscoveredSourceServices]
public partial class MyInMemoryGrpcMockServer
{
}
  1. Use geneated mock server type
await using var mockServer = new MyInMemoryGrpcMockServer(grpcPort: 5033, wireMockPort: 9096);
var connectionInfo = await mockServer.Install();

Option 5: Using source generator + GRPC server stub

  1. Add the following nuget package references
<ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.53.0" />
    <PackageReference Include="WireMock.Net" Version="1.5.25" />
    <PackageReference Include="GrpcTestKit" Version="1.18.0" />
</ItemGroup>
  1. Define partial class for your mock server
[GrpcMockServerFor(typeof(Sample.SampleBase))]
public partial class MyInMemoryGrpcMockServer
{
}
  1. Use geneated mock server type
await using var mockServer = new MyInMemoryGrpcMockServer(grpcPort: 5033, wireMockPort: 9096);
var connectionInfo = await mockServer.Install();

How to prepare mocks

await using var connector = new TestContainerGrpcMockServerConnector( protoDirectory: "protos", grpcPort:5033);

await connector.Install();

var grpcMockClient = connector.CreateClient();

await grpcMockClient.MockRequestReply
(
    serviceName: "my.package.Sample",
    methodName: "TestRequestReply",
    request: new { name = "Hello 1" },
    response: new { message = "Hi there 1" }
);

await grpcMockClient.MockRequestReply
(
    serviceName: "my.package.Sample",
    methodName: "TestRequestReply",
    request: new { name = "Hello 2" },
    response: new { message = "Hi there 2" }
);

await grpcMockClient.MockServerStreaming
(
    serviceName: "my.package.Sample",
    methodName: "TestServerStreaming",
    request: new { name = "Hello streaming" },
    response: new[]
    {
        new {message = "Hi there 1"},
        new {message = "Hi there 2"},
        new {message = "Hi there 3"}
    }
);

await grpcMockClient.MockClientStreaming
(
    serviceName: "my.package.Sample",
    methodName: "TestServerStreaming",
    requests: new []
    {
        new { name = "Hello streaming 1" },
        new { name = "Hello streaming 2" }
    },
    response: new { message = "Hi there streaming client" }
);

await grpcMockClient.MockDuplexStreaming
(
    serviceName: "my.package.Sample",
    methodName: "TestClientServerStreaming", 
    scenario: new MessageExchange[]
    {
        new ()
        {
            Requests = new[]
            {
                new {name = "Ping 1a"},
                new {name = "Ping 1b"}
            },
            Responses = new[]
            {
                new {message = "Pong 1"}
            }
        },
        new ()
        {
            Requests = new[]
            {
                new {name = "Ping 2"},
            },
            Responses = new[]
            {
                new {message = "Pong 2a"},
                new {message = "Pong 2b"}
            }
        },
});

You can also generate stub helpers that will simplify your code responsible for preparing mocks/stubs.

[GrpcMockHelperFor(typeof(Sample.SampleBase))]
public partial class SampleMockHelper
{

}

Now you can prepare your mocks as follows:

await using var connector = new InMemoryGrpcMockServerConnector(grpcPort:5033, wireMockPort: 9594);
                
_ = await connector.Install();

var grpcMockClient = connector.CreateClient();

var mockHelper = new SampleMockHelper(grpcMockClient);

_ = await mockHelper.MockTestRequestReply
(
    request: new HelloRequest {Name = "Hello 1"},
    response: new HelloReply {Message = "Hi there 1"}
);

_ = await mockHelper.MockTestServerStreaming
(
    request: new HelloRequest {Name = "Hello streaming"},
    response: new[]
    {
        new HelloReply {Message = "Hi there 1"},
        new HelloReply {Message = "Hi there 2"},
        new HelloReply {Message = "Hi there 2"},
    }
);

_ = await mockHelper.MockTestClientStreaming
(
    request: new []
    {
        new HelloRequest {Name = "Hello streaming 1"},
        new HelloRequest {Name = "Hello streaming 2"},
    },
    response: new HelloReply
    {
        Message = "Hi there streaming client"
    }
);

_ = await mockHelper.MockTestClientServerStreaming(new MessageExchange<HelloRequest, HelloReply>[]
{
    new()
    {
        Requests = new HelloRequest[]
        {
            new() {Name = "Ping 1a"},
            new() {Name = "Ping 1b"}
        },
        Responses = new HelloReply[]
        {
            new() {Message = "Pong 1"}
        }
    },
    new()
    {
        Requests = new HelloRequest[]
        {
            new() {Name = "Ping 2"},
        },
        Responses = new HelloReply[]
        {
            new() {Message = "Pong 2a"},
            new() {Message = "Pong 2b"}
        }
    },
});

TODO

Alternatives