Skip to content

A gRPC Java testing tool to easily mock endpoints of gRPC services for IT or Unit testing

License

Notifications You must be signed in to change notification settings

lancethomps/grpcmock

 
 

Repository files navigation

gRPC Mock Build pipeline

A gRPC Java testing tool to easily mock endpoints of gRPC services for IT or Unit testing. The tool follows a similar DSL type of structure to HTTP mocking service WireMock.

Features

  • gRPC method stubbing configurable through a fluent Java API
  • Headers and request body matchers to determine the correct stub
  • Configurable delay for individual responses
  • Verifiable method invocations for specific method
  • Supported gRPC method types:
    • Unary methods
    • Server streaming methods
    • Client streaming methods
    • Bidi stream methods

Quick usage

Unary methods

stubFor(unaryMethod(SimpleServiceGrpc.getUnaryRpcMethod())
    .willReturn(response1));

stubFor(unaryMethod(SimpleServiceGrpc.getUnaryRpcMethod())
    .withHeader("header-1", "value-1")
    .withHeader("header-2", value -> value.startsWith("value"))
    .withRequest(expectedRequest)
    .willReturn(response(response1)
        .withFixedDelay(200)) // first invocation will return this response after 200 ms
    .nextWillReturn(response(response2))); // subsequent invocations will return this response

See more examples

Server streaming methods

stubFor(serverStreamingMethod(SimpleServiceGrpc.getServerStreamingRpcMethod())
    .willReturn(responses1, responses2, responses3)); // return one by one with no delay

stubFor(serverStreamingMethod(SimpleServiceGrpc.getServerStreamingRpcMethod())
    .withHeader("header-1", "value-1")
    .withRequest(req -> req.getRequestMessage().endsWith("1"))
    .willReturn(stream(response(responses1).withFixedDelay(200))
        .and(response(responses2).withFixedDelay(100))
        .and(response(responses3).withFixedDelay(200)))
    .nextWillReturn(statusException(Status.NOT_FOUND))); // subsequent invocations will return status exception

See more examples

Client streaming methods

Stubs for client streaming method calls are selected on receiving first stream request message.

stubFor(clientStreamingMethod(SimpleServiceGrpc.getClientStreamingRpcMethod())
    .willReturn(responses1)); // return a response on completed client streaming requests

stubFor(clientStreamingMethod(SimpleServiceGrpc.getClientStreamingRpcMethod())
    .withHeader("header-1", "value-1")
    .withFirstRequest(req -> req.getRequestMessage().endsWith("1"))
    .willReturn(response(responses1).withFixedDelay(200))
    .nextWillReturn(statusException(Status.NOT_FOUND))); // subsequent invocations will return status exception

See more examples

Bidi streaming methods

Stubs for bidi streaming method calls are selected on receiving first stream request message.

stubFor(bidiStreamingMethod(SimpleServiceGrpc.getBidiStreamingRpcMethod())
    .withHeader("header-1", "value-1")
    .withFirstRequest(req -> req.getRequestMessage().endsWith("1"))
    .willProxyTo(responseObserver -> new StreamObserver<SimpleRequest>() {
        @Override
        public void onNext(SimpleRequest request) {
          SimpleResponse response = SimpleResponse.newBuilder()
            .setResponseMessage(request.getRequestMessage())
            .build();
          responseObserver.onNext(response);
        }
        
        @Override
        public void onError(Throwable error) {
          // handle error
        }
        
        @Override
        public void onCompleted() {
          responseObserver.onCompleted();
        }
    }));

See more examples

Verifying invocation count

verifyThat(
    calledMethod(getUnaryRpcMethod())
        .withStatusOk()
        .withHeader("header-1", "value-1")
        .withRequest(request),
    times(3));

verifyThat(getUnaryRpcMethod(), never());

verifyThat(
    calledMethod(getClientStreamingRpcMethod())
        .withNumberOfRequests(2)
        .withFirstRequest(request)
        .withRequestAtIndex(1, request2));

See more examples

Integrations

See example projects

Spring-Boot

gRPC Mock integrates with Spring-Boot via grpcmock-spring-boot module.

<dependency>
  <groupId>org.grpcmock</groupId>
  <artifactId>grpcmock-spring-boot</artifactId>
  <version>0.9.5</version>
</dependency>

You have to declare the @AutoConfigureGrpcMock for the test class to enable gRPC Mock:

@SpringJUnitConfig
@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.NONE)
@AutoConfigureGrpcMock
class TestClass {

  @Value("${grpcmock.server.port}")
  private int grpcMockPort;

  private ManagedChannel channel;

  @BeforeEach
  void setupChannel() {
    channel = ManagedChannelBuilder.forAddress("localhost", grpcMockPort)
        .usePlaintext()
        .build();
  }

  @AfterEach
  void shutdownChannel() {
    Optional.ofNullable(channel).ifPresent(ManagedChannel::shutdownNow);
  }
}

If the gRPC Mock port is set to 0, then a random port will be selected for the server. It is the recommended approach to improve test run times. Once a random port is selected it can be accessed via ${grpcmock.server.port} property and used in gRPC Channel creation.

It's also possible to use InProcess server with Spring-Boot

@SpringJUnitConfig
@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.NONE)
@AutoConfigureGrpcMock(useInProcessServer = true)
class TestClass {

   @Value("${grpcmock.server.name}")
   private String inProcessName;

   private ManagedChannel channel;

   @BeforeEach
   void setupChannel() {
      channel = InProcessChannelBuilder.forName(inProcessName).build();
   }

   @AfterEach
   void shutdownChannel() {
      Optional.ofNullable(channel).ifPresent(ManagedChannel::shutdownNow);
   }
}

If you don't set name() in @AutoConfigureGrpcMock then it will be generated randomly. Beware that it's not possible to create two InProcess servers at once so in case of fixed names you will have to configure a unique name for each spring context. If a random name is used it can be accessed via ${grpcmock.server.name} property and used in gRPC Channel creation.

To remove logging of incoming requests, logging level for GrpcMock should be changed in application-test.yml:

logging.level.org.grpcmock.GrpcMock: WARN

JUnit5

gRPC Mock integrates with JUnit5 via grpcmock-junit5 module.

<dependency>
  <groupId>org.grpcmock</groupId>
  <artifactId>grpcmock-junit5</artifactId>
  <version>0.9.5</version>
</dependency>

You can integrate gRPC Mock with default configuration for a JUnit5 test via @ExtendWith annotation:

@ExtendWith(GrpcMockExtension.class)
class TestClass {

  private ManagedChannel channel;

  @BeforeEach
  void setupChannel() {
    channel = ManagedChannelBuilder.forAddress("localhost", GrpcMock.getGlobalPort())
        .usePlaintext()
        .build();
  }

  @AfterEach
  void shutdownChannel() {
    Optional.ofNullable(channel).ifPresent(ManagedChannel::shutdownNow);
  }
}

For quicker unit test execution you can integrate gRPC Mock using an in-process server via InProcessGrpcMockExtension extension:

@ExtendWith(InProcessGrpcMockExtension.class)
class TestClass {

  private ManagedChannel channel;

  @BeforeEach
  void setupChannel() {
    channel = InProcessChannelBuilder.forName(GrpcMock.getGlobalInProcessName())
        .usePlaintext()
        .build();
  }

  @AfterEach
  void shutdownChannel() {
    Optional.ofNullable(channel).ifPresent(ManagedChannel::shutdownNow);
  }
}

Alternatively, you can configure gRPC Mock programmatically using @RegisterExtension annotation:

class TestClass {

  @RegisterExtension
  static GrpcMockExtension grpcMockExtension = GrpcMockExtension.builder()
      .withPort(0)
      .withInterceptor(new MyServerInterceptor())
      .build();
  private ManagedChannel channel;

  @BeforeEach
  void setupChannel() {
    channel = ManagedChannelBuilder.forAddress("localhost", GrpcMock.getGlobalPort())
        .usePlaintext()
        .build();
  }

  @AfterEach
  void shutdownChannel() {
    Optional.ofNullable(channel).ifPresent(ManagedChannel::shutdownNow);
  }
}

In both variants the port for the gRPC Mock server can be retrieved via GrpcMock.getGlobalPort(). Mapping stubs will be cleared after each test run and after all tests in the test class are done the server will be shutdown.

To remove logging of incoming requests, logging level for GrpcMock should be changed depending on the logging backend used. E.g. when using slf4j-simple backend a file simplelogger.properties needs to be created in test resouces with content:

org.slf4j.simpleLogger.log.org.grpcmock.GrpcMock=warn

About

A gRPC Java testing tool to easily mock endpoints of gRPC services for IT or Unit testing

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 100.0%