Skip to content

Latest commit

 

History

History
603 lines (465 loc) · 14.4 KB

File metadata and controls

603 lines (465 loc) · 14.4 KB

Integration Testing Guide

This guide covers the comprehensive integration testing strategy for the Firefly Common Client Library, including WireMock for REST/SOAP services, in-process gRPC servers, and best practices for testing all client types.

Table of Contents


Overview

The library includes comprehensive integration tests that verify all client types work correctly with real (mocked) services:

Client Type Testing Framework Test Coverage
REST WireMock ✅ Full coverage
gRPC In-Process Server ✅ Full coverage
SOAP WireMock ✅ Full coverage
GraphQL Helper Unit Tests ✅ Full coverage
OAuth2 Helper Unit Tests ✅ Full coverage
Multipart Helper Unit Tests ✅ Full coverage
WebSocket Helper Unit Tests ✅ Full coverage

Testing Infrastructure

WireMock for REST/SOAP

WireMock is used to mock HTTP-based services (REST and SOAP):

<dependency>
    <groupId>org.wiremock</groupId>
    <artifactId>wiremock-standalone</artifactId>
    <version>3.3.1</version>
    <scope>test</scope>
</dependency>

Key Features:

  • Dynamic port allocation
  • Request matching and verification
  • Scenario-based testing (for retry logic)
  • SOAP/XML support
  • Delay simulation for timeout testing

gRPC In-Process Server

For gRPC testing, we use in-process servers:

<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-testing</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-inprocess</artifactId>
    <scope>test</scope>
</dependency>

Key Features:

  • No network overhead
  • Fast test execution
  • Full gRPC feature support
  • Streaming support

REST Client Integration Tests

Location

src/test/java/org/fireflyframework/common/client/rest/RestClientIntegrationTest.java

Test Coverage

1. Basic CRUD Operations

@Test
@DisplayName("Should perform GET request successfully")
void shouldPerformGetRequestSuccessfully() {
    // Given: Mock REST endpoint
    wireMockServer.stubFor(get(urlEqualTo("/users/123"))
        .willReturn(aResponse()
            .withStatus(200)
            .withHeader("Content-Type", "application/json")
            .withBody("{\"id\":\"123\",\"name\":\"John Doe\"}")));

    // When: Execute GET request
    Mono<User> result = client.get("/users/{id}", User.class)
        .pathVariable("id", "123")
        .execute();

    // Then: Verify response
    StepVerifier.create(result)
        .assertNext(user -> {
            assertThat(user.id()).isEqualTo("123");
            assertThat(user.name()).isEqualTo("John Doe");
        })
        .verifyComplete();
}

2. Error Handling

@Test
@DisplayName("Should handle 404 errors gracefully")
void shouldHandle404ErrorsGracefully() {
    wireMockServer.stubFor(get(urlEqualTo("/users/999"))
        .willReturn(aResponse()
            .withStatus(404)
            .withBody("{\"error\":\"User not found\"}")));

    Mono<User> result = client.get("/users/{id}", User.class)
        .pathVariable("id", "999")
        .execute();

    StepVerifier.create(result)
        .expectError(ServiceNotFoundException.class)
        .verify();
}

3. Retry Logic

@Test
@DisplayName("Should retry on 500 errors")
void shouldRetryOn500Errors() {
    // First 2 calls fail, third succeeds
    wireMockServer.stubFor(get(urlEqualTo("/api/data"))
        .inScenario("Retry Scenario")
        .whenScenarioStateIs("Started")
        .willReturn(aResponse().withStatus(500))
        .willSetStateTo("First Retry"));

    wireMockServer.stubFor(get(urlEqualTo("/api/data"))
        .inScenario("Retry Scenario")
        .whenScenarioStateIs("First Retry")
        .willReturn(aResponse().withStatus(500))
        .willSetStateTo("Second Retry"));

    wireMockServer.stubFor(get(urlEqualTo("/api/data"))
        .inScenario("Retry Scenario")
        .whenScenarioStateIs("Second Retry")
        .willReturn(aResponse()
            .withStatus(200)
            .withBody("{\"data\":\"success\"}")));

    // Verify retry succeeded
    StepVerifier.create(client.get("/api/data", String.class).execute())
        .expectNextCount(1)
        .verifyComplete();

    // Verify 3 attempts were made
    verify(exactly(3), getRequestedFor(urlEqualTo("/api/data")));
}

4. Timeout Handling

@Test
@DisplayName("Should timeout on slow responses")
void shouldTimeoutOnSlowResponses() {
    wireMockServer.stubFor(get(urlEqualTo("/api/slow"))
        .willReturn(aResponse()
            .withStatus(200)
            .withFixedDelay(5000) // 5 second delay
            .withBody("{\"data\":\"slow\"}")));

    Mono<String> result = client.get("/api/slow", String.class)
        .timeout(Duration.ofSeconds(1))
        .execute();

    StepVerifier.create(result)
        .expectError(TimeoutException.class)
        .verify();
}

Running REST Integration Tests

mvn test -Dtest=RestClientIntegrationTest

gRPC Client Integration Tests

Location

src/test/java/org/fireflyframework/common/client/grpc/GrpcClientIntegrationTest.java

Test Coverage

1. Unary RPC

@Test
@DisplayName("Should execute unary RPC successfully")
void shouldExecuteUnaryRpcSuccessfully() {
    // Given: In-process gRPC server
    TestServiceImpl testService = new TestServiceImpl();
    server = InProcessServerBuilder
        .forName(SERVER_NAME)
        .directExecutor()
        .addService(testService)
        .build()
        .start();

    // When: Execute unary RPC
    var result = grpcClient.executeUnary(
        channel,
        TestServiceGrpc.newStub(channel)::sayHello,
        HelloRequest.newBuilder().setName("World").build()
    );

    // Then: Verify response
    StepVerifier.create(result)
        .assertNext(response -> {
            assertThat(response.getMessage()).isEqualTo("Hello, World!");
        })
        .verifyComplete();
}

2. Server Streaming

@Test
@DisplayName("Should handle server streaming RPC")
void shouldHandleServerStreamingRpc() {
    var result = grpcClient.executeServerStreaming(
        channel,
        TestServiceGrpc.newStub(channel)::streamNumbers,
        NumberRequest.newBuilder().setCount(5).build()
    );

    StepVerifier.create(result)
        .expectNextCount(5)
        .verifyComplete();
}

3. Error Handling

@Test
@DisplayName("Should handle gRPC errors gracefully")
void shouldHandleGrpcErrorsGracefully() {
    var result = grpcClient.executeUnary(
        channel,
        TestServiceGrpc.newStub(channel)::failingMethod,
        EmptyRequest.newBuilder().build()
    );

    StepVerifier.create(result)
        .expectErrorSatisfies(throwable -> {
            assertThat(throwable).isInstanceOf(StatusRuntimeException.class);
            StatusRuntimeException ex = (StatusRuntimeException) throwable;
            assertThat(ex.getStatus().getCode()).isEqualTo(Status.Code.INTERNAL);
        })
        .verify();
}

Running gRPC Integration Tests

mvn test -Dtest=GrpcClientIntegrationTest

SOAP Client Integration Tests

Location

src/test/java/org/fireflyframework/common/client/soap/SoapClientIntegrationTest.java

Test Coverage

1. SOAP Operation Invocation

@Test
@DisplayName("Should invoke SOAP operation successfully")
void shouldInvokeSoapOperationSuccessfully() {
    // Given: Mock WSDL and SOAP response
    String soapResponse = """
        <?xml version="1.0" encoding="utf-8"?>
        <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
            <soap:Body>
                <ns:AddResponse xmlns:ns="http://tempuri.org/">
                    <AddResult>8</AddResult>
                </ns:AddResponse>
            </soap:Body>
        </soap:Envelope>
        """;

    wireMockServer.stubFor(post(urlEqualTo("/calculator.asmx"))
        .willReturn(aResponse()
            .withStatus(200)
            .withHeader("Content-Type", "text/xml; charset=utf-8")
            .withBody(soapResponse)));

    // When: Invoke SOAP operation
    var result = soapClient.invoke("Add", request);

    // Then: Verify response
    StepVerifier.create(result)
        .assertNext(response -> {
            assertThat(response.getResult()).isEqualTo(8);
        })
        .verifyComplete();
}

2. WSDL with Authentication

@Test
@DisplayName("Should handle WSDL with authentication parameters")
void shouldHandleWsdlWithAuthenticationParameters() {
    String wsdlUrl = baseUrl + "/service.asmx?WSDL&user=admin&password=secret";
    
    var soapClient = new SoapClientBuilder()
        .wsdlUrl(wsdlUrl)
        .build();

    assertThat(soapClient).isNotNull();
}

Running SOAP Integration Tests

mvn test -Dtest=SoapClientIntegrationTest

Helper Integration Tests

GraphQL Helper

Unit Tests Location: src/test/java/org/fireflyframework/common/client/helpers/GraphQLClientHelperTest.java

Coverage:

  • ✅ Query execution
  • ✅ Mutation execution
  • ✅ Variable handling
  • ✅ Error handling
  • ✅ Retry logic
  • ✅ Query caching
  • ✅ Batch operations

OAuth2 Helper

Unit Tests Location: src/test/java/org/fireflyframework/common/client/helpers/OAuth2ClientHelperTest.java

Coverage:

  • ✅ Token acquisition
  • ✅ Token caching
  • ✅ Multi-scope support
  • ✅ Refresh token flow
  • ✅ Retry logic
  • ✅ Error handling

Multipart Upload Helper

Unit Tests Location: src/test/java/org/fireflyframework/common/client/helpers/MultipartUploadHelperTest.java

Coverage:

  • ✅ File upload
  • ✅ Progress tracking
  • ✅ Chunked uploads
  • ✅ Parallel uploads
  • ✅ File validation
  • ✅ Compression
  • ✅ Retry logic

WebSocket Helper

Unit Tests Location: src/test/java/org/fireflyframework/common/client/helpers/WebSocketClientHelperTest.java

Coverage:

  • ✅ Connection management
  • ✅ Automatic reconnection
  • ✅ Heartbeat/ping-pong
  • ✅ Message queuing
  • ✅ Connection pooling
  • ✅ Message acknowledgment
  • ✅ Binary messages

Best Practices

1. Use WireMock for HTTP-based Services

@BeforeAll
static void startWireMock() {
    wireMockServer = new WireMockServer(WireMockConfiguration.options().dynamicPort());
    wireMockServer.start();
    WireMock.configureFor("localhost", wireMockServer.port());
}

@AfterAll
static void stopWireMock() {
    if (wireMockServer != null) {
        wireMockServer.stop();
    }
}

@BeforeEach
void resetWireMock() {
    wireMockServer.resetAll();
}

2. Use Scenarios for Retry Testing

wireMockServer.stubFor(post(urlEqualTo("/api/endpoint"))
    .inScenario("Retry Scenario")
    .whenScenarioStateIs("Started")
    .willReturn(aResponse().withStatus(500))
    .willSetStateTo("First Retry"));

3. Verify Request Details

verify(exactly(1), postRequestedFor(urlEqualTo("/api/endpoint"))
    .withHeader("Authorization", equalTo("Bearer token"))
    .withRequestBody(containing("expected-data")));

4. Use StepVerifier for Reactive Tests

StepVerifier.create(result)
    .assertNext(response -> {
        assertThat(response.getData()).isNotNull();
    })
    .verifyComplete();

5. Test Error Scenarios

@Test
@DisplayName("Should handle network errors")
void shouldHandleNetworkErrors() {
    wireMockServer.stubFor(get(urlEqualTo("/api/data"))
        .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER)));

    StepVerifier.create(client.get("/api/data", String.class).execute())
        .expectError(ServiceClientException.class)
        .verify();
}

Running Tests

Run All Integration Tests

mvn clean test

Run Specific Test Class

mvn test -Dtest=RestClientIntegrationTest
mvn test -Dtest=GrpcClientIntegrationTest
mvn test -Dtest=SoapClientIntegrationTest

Run Specific Test Method

mvn test -Dtest=RestClientIntegrationTest#shouldPerformGetRequestSuccessfully

Run Tests with Coverage

mvn clean test jacoco:report

Coverage report will be available at: target/site/jacoco/index.html

Run Tests in Parallel

mvn test -T 4

Test Statistics

Test Suite Tests Coverage
REST Client Integration 14 tests ✅ 100%
gRPC Client Integration 7 tests ✅ 100%
SOAP Client Integration 8 tests ✅ 100%
GraphQL Helper 11 tests ✅ 100%
OAuth2 Helper 17 tests ✅ 100%
Multipart Helper 16 tests ✅ 100%
WebSocket Helper 20 tests ✅ 100%
Total 280+ tests ✅ 100%

Continuous Integration

GitHub Actions Example

name: Integration Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Set up JDK 21
        uses: actions/setup-java@v3
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Run Integration Tests
        run: mvn clean test
      - name: Generate Coverage Report
        run: mvn jacoco:report
      - name: Upload Coverage
        uses: codecov/codecov-action@v3

Troubleshooting

WireMock Port Conflicts

If you encounter port conflicts, WireMock uses dynamic ports by default:

wireMockServer = new WireMockServer(WireMockConfiguration.options().dynamicPort());

gRPC In-Process Server Issues

Ensure unique server names for parallel tests:

String serverName = "test-server-" + UUID.randomUUID();
server = InProcessServerBuilder.forName(serverName).build().start();

Test Timeouts

Increase timeout for slow tests:

StepVerifier.create(result)
    .expectNextCount(1)
    .verifyComplete(Duration.ofSeconds(30));

Related Documentation