mirror of
https://github.com/docker/docs.git
synced 2026-03-28 23:08:49 +07:00
## 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>
167 lines
5.4 KiB
Markdown
167 lines
5.4 KiB
Markdown
---
|
|
title: Write tests with Testcontainers MockServer
|
|
linkTitle: Write tests
|
|
description: Test external REST API integrations using the Testcontainers MockServer module and REST Assured.
|
|
weight: 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](https://www.mock-server.com/) instance inside a Docker container.
|
|
You can then use `MockServerClient` to configure mock expectations.
|
|
|
|
## Write the test
|
|
|
|
Create `AlbumControllerTest.java`:
|
|
|
|
```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.
|