Skip to content

Fadelis/grpcmock

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

Working in multithreaded test setup

When working in multithreaded test setup, where the test class is initiated on one thread and test methods are called on a different one, you will encounter UNIMPLEMENTED errors when trying to use the static configuration methods. GrpcMock uses a ThreadLocal instance of GrpcMock when using all the static methods, so it will be a different instance on different threads.

To work with GrpcMock in such cases you'll need to have access to the grpcMock instance object in your test class.

In Spring-Boot setup:

@SpringJUnitConfig
@SpringBootTest(classes = Application.class, webEnvironment = WebEnvironment.NONE)
@AutoConfigureGrpcMock
class TestClass {
  
  @Autowire
  private GrpcMock grpcMock;
}

In other setups:

class TestClass {
  
  private GrpcMock grpcMock = GrpcMock.grpcMock().build().start();
}

Then when configuring your stubs you can either call the non-static methods directly on the instance in your test methods:

@Test
void should_test_something() {
  grpcMock.register(unaryMethod(SimpleServiceGrpc.getUnaryRpcMethod())
        .willReturn(response));
  
  ... test code
        
  grpcMock.verifyThat(calledMethod(SimpleServiceGrpc.getUnaryRpcMethod()).build(), CountMatcher.once());
}

Alternatively you could call GrpcMock.configureFor method with the grpcMock instance in the test class at the beginning of each test:

@Test
void should_test_something() {
  configureFor(grpMock);
  stubFor(unaryMethod(SimpleServiceGrpc.getUnaryRpcMethod())
        .willReturn(response));
  
  ... test code
        
  verifyThat(calledMethod(SimpleServiceGrpc.getUnaryRpcMethod()));
}

Or depending whether BeforeEach method is called on the same thread as the test method that could be done in BeforeEach method:

@BeforeEach
void setup() {
  configureFor(grpcMock);
}