Tests with Grpc-Stubs

<- Back to Index

This section describes how you write tests for components that use the @GrpcClient annotation or grpc’s stubs.

Table of Contents

Additional Topics

Introductory Words

Generally there are two ways to test your component containing a grpc stub:

Note: There are very important differences in both variants that might affect you during the tests. Please consider the pros and cons listed at each on the variants carefully.

The Component to test

Let’s assume that we wish to test the following component:

@Component
public class MyComponent {

    private ChatServiceBlockingStub chatService;

    @GrpcClient("chatService")
    public void setChatService(ChatServiceBlockingStub chatService) {
        this.chatService = chatService;
    }

    public String sayHello(String name) {
        HelloRequest request = HelloRequest.newBuilder()
                .setName(name)
                .build();
        HelloReply reply = chatService.sayHello(name)
        return reply.getMessage();
    }

}

Useful Dependencies

Before you start writing your own test framework, you might want to use the following libraries to make your work easier.

Note: Spring-Boot-Test already contains some of these dependencies, so make sure you exclude conflicting versions.

For Maven add the following dependencies:

<!-- JUnit-Test-Framework -->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <scope>test</scope>
</dependency>
<!-- Grpc-Test-Support -->
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-testing</artifactId>
    <scope>test</scope>
</dependency>
<!-- Spring-Test-Support (Optional) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <!-- Exclude the test engine you don't need -->
    <exclusions>
        <exclusion>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- Mocking Framework (Optional) -->
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-all</artifactId>
    <scope>test</scope>
</dependency>

For Gradle use:

// JUnit-Test-Framework
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
// Grpc-Test-Support
testImplementation("io.grpc:grpc-testing")
// Spring-Test-Support (Optional)
testImplementation("org.springframework.boot:spring-boot-starter-test") {
    // Exclude the test engine you don't need
    exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
// Mocking Framework (Optional)
testImplementation("org.mockito:mockito-all")

Using a Mocked Stub

In order to test the method we mock the stub and inject it using a setter.

Pros

Cons

Implementation

  1. Add mockito to our dependencies (see above)
  2. Configure mockito to work with final classes/methods

    For this we need to create a file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker containing:

    mock-maker-inline
    
  3. Write our mocks as usual and explicitly set it on your component in test

    public class MyComponentTest {
    
        private MyComponent myComponent = new MyComponent();
        private ChatServiceBlockingStub chatService = Mockito.mock(ChatServiceBlockingStub.class);
    
        @BeforeEach
        void setup() {
            myComponent.setChatService(chatService);
        }
    
        @Test
        void testSayHello() {
            Mockito.when(chatService.sayHello(...)).thenAnswer(...);
            assertThat(myComponent.sayHello("ThisIsMyName")).contains("ThisIsMyName");
        }
    
    }
    

Running a Dummy Server

In order to test the method we start a grpc server ourselves and connect to it during our tests.

Pros

Cons

Implementation

The actual implementation of the test might look somewhat like this:

@SpringBootTest(properties = {
        "grpc.server.inProcessName=test", // Enable inProcess server
        "grpc.server.port=-1", // Disable external server
        "grpc.client.chatService.address=in-process:test" // Configure the client to connect to the inProcess server
        })
@SpringJUnitConfig(classes = { MyComponentIntegrationTestConfiguration.class })
// Spring doesn't start without a config (might be empty)
@DirtiesContext // Ensures that the grpc-server is properly shutdown after each test
        // Avoids "port already in use" during tests
public class MyComponentTest {

    @Autowired
    private MyComponent myComponent;

    @Test
    @DirtiesContext
    void testSayHello() {
        assertThat(myComponent.sayHello("ThisIsMyName")).contains("ThisIsMyName");
    }

}

and the required configuration looks like this:

@Configuration
@ImportAutoConfiguration({
        GrpcServerAutoConfiguration.class, // Create required server beans
        GrpcServerFactoryAutoConfiguration.class, // Select server implementation
        GrpcClientAutoConfiguration.class}) // Support @GrpcClient annotation
public class MyComponentIntegrationTestConfiguration {

    @Bean
    MyComponent myComponent() {
        return new MyComponent();
    }

    @Bean
    ChatServiceImplForMyComponentIntegrationTest chatServiceImpl() {
        return new ChatServiceImplForMyComponentIntegrationTest();
    }

}

and the dummy service implementation might look like this:

@GrpcService
public class ChatServiceImplForMyComponentIntegrationTest extends ChatServiceGrpc.ChatServiceImplBase {

    @Override
    public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
        HelloReply response = HelloReply.newBuilder()
                .setMessage("Hello ==> " + request.getName())
                .build();
        responseObserver.onNext(response);
        responseObserver.onComplete();
    }

    // Methods that aren't used in the test don't need to be implemented

}

Additional Topics


<- Back to Index