Java / Gradle

Testing REST API integrations using Testcontainers with WireMock or MockServer

This guide shows how to test an external API integration using Testcontainers WireMock module.

Sergio del Amo
On this guide
In this section

Getting Started

In this guide, you will learn how to

What you will need

To complete this guide, you will need the following:

Solution

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

What we are going to achieve in this guide

We will create a Micronaut application that talks to an external REST API.

Then, we will test the external REST API integration using both the Testcontainers WireMock module and MockServer.

Writing the Application

Create an application using the Micronaut Command Line Interface or with Micronaut Launch.

mn create-app example.micronaut.micronautguide \
    --features=http-client,micronaut-test-rest-assured,testcontainers \
    --build=gradle \
    --lang=java \
    --test=junit
Note
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool.
If you don’t specify the --lang argument, Java is used as the language.
If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.

The previous command creates a Micronaut application with the default package example.micronaut in a directory named micronautguide.

If you use Micronaut Launch, select Micronaut Application as application type and add http-client, micronaut-test-rest-assured, and testcontainers features.

Note
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application.

About the application

Assume we are building an application to manage video albums, and we will use a 3rd party REST API to manage the image and video assets. For this guide, we will use a publicly available REST API jsonplaceholder.typicode.com as a 3rd party photo-service to store album photos.

We will implement a REST API endpoint to fetch an album for the given albumId. This API internally talks to the photo-service to fetch the photos for that album.

We will use WireMock, which is a tool for building mock APIs, to mock the external service interactions and test our API endpoints. Testcontainers provides the Testcontainers WireMock module so that we can run WireMock as a Docker container.

Create Album and Photo models

First, create Album and Photo models using Java records.

java/src/main/java/example/micronaut/Photo.java
java/src/main/java/example/micronaut/Album.java

Create PhotoServiceClient

Let’s create PhotoServiceClient, which is a Micronaut declarative HTTP Client, to fetch photos for a given albumId.

java/src/main/java/example/micronaut/PhotoServiceClient.java

We have externalized the photo-service base URL as a configurable property. So, let us add the following property in the src/main/resources/application.properties file.

java/src/main/resources/application.properties
micronaut.http.services.photosapi.url=https://jsonplaceholder.typicode.com

Implement API endpoint to get an album by id

Let us implement a REST API endpoint to return an Album for the given albumId as follows:

java/src/main/java/example/micronaut/AlbumController.java

Our application is exposing a REST API endpoint GET /api/albums/{albumId} which internally makes an API call to https://jsonplaceholder.typicode.com/albums/{albumId}/photos to get photos of that album, and it returns a response similar to the following:

{
   "albumId": 1,
   "photos": [
       {
           "id": 51,
           "title": "non sunt voluptatem placeat consequuntur rem incidunt",
           "url": "https://via.placeholder.com/600/8e973b",
           "thumbnailUrl": "https://via.placeholder.com/150/8e973b"
       },
       {
           "id": 52,
           "title": "eveniet pariatur quia nobis reiciendis laboriosam ea",
           "url": "https://via.placeholder.com/600/121fa4",
           "thumbnailUrl": "https://via.placeholder.com/150/121fa4"
       },
       ...
       ...
   ]
}

You can run the application and access http://localhost:8080/api/albums/1 to see the JSON response.

Let us see how we can test the photo-service API integration using WireMock.

Testing

Write a test for Photo API integration

It is better to mock the external API interactions at the HTTP protocol level instead of mocking the photoServiceClient.getPhotos(albumId) method because you will be able to verify any marshalling/unmarshalling errors, simulating network latency issues, etc.

Add the WireMock Standalone dependency to your project:

build.gradle
testImplementation("org.wiremock:wiremock-standalone:@wiremock-standaloneVersion@")
Note
Add the repository https://jitpack.io to your build file to resolve the previous dependency.

Let us write the test for our GET /api/albums/{albumId} API endpoint as follows:

java/src/test/java/example/micronaut/AlbumControllerTest.java

Stubbing using JSON mapping files

Add the Testcontainers Java modules for WireMock dependency to your project:

build.gradle
testImplementation("com.github.wiremock:wiremock-testcontainers-java:@wiremock-testcontainers-javaVersion@")

In the previous test, we saw how to stub an API using wireMock.stubFor(…​). Instead of stubbing using WireMock Java API, we can use JSON mapping-based configuration.

Create src/test/resources/wiremock/mappings/get-album-photos.json file as follows:

java/src/test/resources/wiremock/mappings/get-album-photos.json
{
  "mappings": [
    {
      "request": {
        "method": "GET",
        "urlPattern": "/albums/([0-9]+)/photos"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json"
        },
        "bodyFileName": "album-photos-resp-200.json"
      }
    },
    {
      "request": {
        "method": "GET",
        "urlPattern": "/albums/2/photos"
      },
      "response": {
        "status": 500,
        "headers": {
          "Content-Type": "application/json"
        }
      }
    },
    {
      "request": {
        "method": "GET",
        "urlPattern": "/albums/3/photos"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json"
        },
        "jsonBody": []
      }
    }
  ]
}

Now you can initialize WireMock by loading the stub mappings from mapping files as follows:

java/src/test/java/example/micronaut/AlbumControllerWireMockMappingTests.java
@RegisterExtension
static WireMockExtension wireMockServer = WireMockExtension.newInstance()
        .options(wireMockConfig().dynamicPort().usingFilesUnderClasspath("wiremock"))
        .build();

With mapping files-based stubbing in place, you can write tests as follows:

java/src/test/java/example/micronaut/AlbumControllerWireMockMappingTests.java
@Test
void shouldGetAlbumById() {
    Long albumId = 1L;
    try (EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class, getProperties())) {
        RestAssured.port = server.getPort();

        given().contentType(ContentType.JSON)
                .when()
                .get("/api/albums/{albumId}", albumId)
                .then()
                .statusCode(200)
                .body("albumId", is(albumId.intValue()))
                .body("photos", hasSize(2));

    }
}

Using Testcontainers WireMock Module

The Testcontainers WireMock module allows provisioning the WireMock server as a standalone container within your tests, based on WireMock Docker.

Create AlbumControllerTestcontainersTests and use WireMockContainer to initialize a wiremock server and stubbing as follows:

java/src/test/java/example/micronaut/AlbumControllerTestcontainersTests.java

Create src/test/resources/example/micronaut/AlbumControllerTestcontainersTests/mocks-config.json file as follows:

java/src/test/resources/example/micronaut/AlbumControllerTestcontainersTests/mocks-config.json
{
  "mappings": [
    {
      "request": {
        "method": "GET",
        "urlPattern": "/albums/([0-9]+)/photos"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json"
        },
        "bodyFileName": "album-photos-response.json"
      }
    },
    {
      "request": {
        "method": "GET",
        "urlPattern": "/albums/2/photos"
      },
      "response": {
        "status": 500,
        "headers": {
          "Content-Type": "application/json"
        }
      }
    },
    {
      "request": {
        "method": "GET",
        "urlPattern": "/albums/3/photos"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json"
        },
        "jsonBody": []
      }
    }
  ]
}

If you run the test, the call to photo API will receive the response using WireMock stubbings defined in mocks-config.json file.

Testing with MockServer

For any system you integrate with via HTTP or HTTPS MockServer can be used as a mock configured to return specific responses for different requests, a proxy recording and optionally modifying requests and responses, both a proxy for some requests and a mock for other requests at the same time.

MockServer dependencies

Add the Testcontainers MockServer dependency:

build.gradle
testImplementation("org.testcontainers:testcontainers-mockserver")

Add the MockServer Java Client dependency:

build.gradle
testImplementation("org.mock-server:mockserver-client-java:@mockserver-client-javaVersion@")

MockServer Test

You can write a test using MockServer as follows:

java/src/test/java/example/micronaut/AlbumControllerMockServerTest.java

Testing the Application

To run the tests:

./gradlew test

Then open build/reports/tests/test/index.html in a browser to see the results.

Now, if you run your test, you should see in the console log that WireMock Docker instance is started which will act as the photo-service, serving the mock responses as per the configured expectations, and the test should pass.

Summary

We have learned how to integrate 3rd party HTTP APIs in a Micronaut application and test it using Testcontainers WireMock module or MockServer.

Next Steps

Refer to Testcontainers WireMock module’s documentation for more information.

Learn more about Micronaut Test and Testcontainers.

Help with the Micronaut Framework

The Micronaut Foundation sponsored the creation of this Guide. A variety of consulting and support services are available.

License

Note
All guides are released with an Apache License 2.0 for the code and a Creative Commons Attribution 4.0 license for the writing and media (images).