Files
Manuel de la Peña b951e92f57 feat(guides): migrate all testcontainers.com guides (#24505)
## Description

Migrate 17 Testcontainers guides from testcontainers.com into the Docker
docs site, covering Java (14 guides), .NET (2 guides), and Node.js (1
guide). This follows up on PR #24450 which added the initial Go and
Python guides.

Each guide is converted from AsciiDoc to Hugo Markdown, split into
multi-chapter stepper navigation, updated to the latest Testcontainers
API, and verified with passing tests running in containers.

Java guides use testcontainers-java 2.0.4 with the new 2.x Maven
coordinates and package names (e.g., `testcontainers-postgresql`,
`org.testcontainers.postgresql.PostgreSQLContainer`). The Quarkus guide
uses Quarkus 3.22.3 with TC 1.x managed by the Quarkus BOM, since no
released Quarkus version ships TC 2.x yet.

## How to test

All code snippets have been verified by running each guide's source
repository tests inside Docker containers with the Docker socket
mounted.

To re-run the verification, use the `/testcontainers-guides-migrator`
skill included in this PR
(`.claude/skills/testcontainers-guides-migrator/SKILL.md`). The skill's
Step 6 documents the exact container commands and macOS Docker Desktop
workarounds (host override, docker-java API version, etc.) needed to run
each language's tests:

```
/testcontainers-guides-migrator I want you to verify all the guides in this branch.
Do a full review, verifying that all code snippets compile, the code is executable,
and ALL the tests pass. Run them as docker containers, never locally.
```

## Related issues or tickets

Supersedes #24450 (expanded from 2 guides to all 19)

## Reviews

- [ ] Technical review
- [ ] Editorial review
- [ ] Product review

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 10:03:26 +00:00

5.4 KiB

title, linkTitle, description, weight
title linkTitle description weight
Write tests with Testcontainers MockServer Write tests Test external REST API integrations using the Testcontainers MockServer module and REST Assured. 20

Mocking external API interactions at the HTTP protocol level, rather than mocking Java methods, lets you verify marshalling and unmarshalling behavior and simulate network issues.

Testcontainers provides a MockServer module that starts a MockServer instance inside a Docker container. You can then use MockServerClient to configure mock expectations.

Write the test

Create AlbumControllerTest.java:

package com.testcontainers.demo;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.hasSize;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;
import static org.mockserver.model.JsonBody.json;

import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockserver.client.MockServerClient;
import org.mockserver.model.Header;
import org.mockserver.verify.VerificationTimes;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.mockserver.MockServerContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class AlbumControllerTest {

  @LocalServerPort
  private Integer port;

  @Container
  static MockServerContainer mockServerContainer =
    new MockServerContainer("mockserver/mockserver:5.15.0");

  static MockServerClient mockServerClient;

  @DynamicPropertySource
  static void overrideProperties(DynamicPropertyRegistry registry) {
    mockServerClient =
    new MockServerClient(
      mockServerContainer.getHost(),
      mockServerContainer.getServerPort()
    );
    registry.add("photos.api.base-url", mockServerContainer::getEndpoint);
  }

  @BeforeEach
  void setUp() {
    RestAssured.port = port;
    mockServerClient.reset();
  }

  @Test
  void shouldGetAlbumById() {
    Long albumId = 1L;

    mockServerClient
      .when(
        request().withMethod("GET").withPath("/albums/" + albumId + "/photos")
      )
      .respond(
        response()
          .withStatusCode(200)
          .withHeaders(
            new Header("Content-Type", "application/json; charset=utf-8")
          )
          .withBody(
            json(
              """
              [
                   {
                       "id": 1,
                       "title": "accusamus beatae ad facilis cum similique qui sunt",
                       "url": "https://via.placeholder.com/600/92c952",
                       "thumbnailUrl": "https://via.placeholder.com/150/92c952"
                   },
                   {
                       "id": 2,
                       "title": "reprehenderit est deserunt velit ipsam",
                       "url": "https://via.placeholder.com/600/771796",
                       "thumbnailUrl": "https://via.placeholder.com/150/771796"
                   }
               ]
              """
            )
          )
      );

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

    verifyMockServerRequest("GET", "/albums/" + albumId + "/photos", 1);
  }

  @Test
  void shouldReturn404StatusWhenAlbumNotFound() {
    Long albumId = 1L;
    mockServerClient
      .when(
        request().withMethod("GET").withPath("/albums/" + albumId + "/photos")
      )
      .respond(response().withStatusCode(404));

    given()
      .contentType(ContentType.JSON)
      .when()
      .get("/api/albums/{albumId}", albumId)
      .then()
      .statusCode(404);

    verifyMockServerRequest("GET", "/albums/" + albumId + "/photos", 1);
  }

  private void verifyMockServerRequest(String method, String path, int times) {
    mockServerClient.verify(
      request().withMethod(method).withPath(path),
      VerificationTimes.exactly(times)
    );
  }
}

Here's what the test does:

  • @SpringBootTest starts the full application on a random port.
  • The @Testcontainers and @Container annotations start a MockServerContainer and create a MockServerClient connected to it.
  • @DynamicPropertySource overrides photos.api.base-url to point at the MockServer endpoint, so the application talks to MockServer instead of the real photo service.
  • @BeforeEach resets the MockServerClient before every test so that expectations from one test don't affect another.
  • shouldGetAlbumById() configures a mock response for /albums/{albumId}/photos, sends a request to the application's /api/albums/{albumId} endpoint, and verifies the response body. It also uses mockServerClient.verify() to confirm that the expected API call reached MockServer.
  • shouldReturn404StatusWhenAlbumNotFound() configures MockServer to return a 404 status and verifies the application propagates that status to the caller.