Java Testcontainers
GitHubOfficialFirst committed by jduboisDistributed by GitHub
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides guidance on implementing integration tests in Java using Testcontainers 2.x. It covers the setup of containerized dependencies like databases, message brokers, and caches, ensuring tests run against real infrastructure rather than mocks. The skill emphasizes framework-neutral practices, lifecycle management, wait strategies, and CI/CD integration, while highlighting key changes from the 1.x version.
What it does
- Configures containerized dependencies for integration testing in Java.
- Provides patterns for container lifecycle management including singleton and per-method setups.
- Defines wait strategies to ensure services are ready before tests execute.
- Supports multi-container networking and Docker Compose integration.
- Offers troubleshooting steps for CI environments and Docker/Podman configurations.
- Outlines migration paths and dependency management for Testcontainers 2.x.
When to use it
- When you need to write integration tests that require real database, cache, or broker instances.
- When migrating existing integration tests from Testcontainers 1.x to 2.x.
- When you need to ensure test environments match production infrastructure.
- When setting up CI pipelines that require reliable, containerized test dependencies.
Advantages
- Runs tests against real production-like dependencies.
- Framework-neutral implementation works with various Java testing setups.
- Provides robust lifecycle control to balance test speed and isolation.
- Reduces flakiness by using explicit wait strategies instead of arbitrary sleeps.
Usage examples
- “How do I set up a PostgreSQL container for my JUnit 5 integration test?”
- The agent provides a code snippet using the @Testcontainers and @Container annotations, showing how to define a static PostgreSQLContainer and retrieve the JDBC URL.
- “My integration test is failing because the database isn't ready yet. How do I fix this?”
- The agent explains how to implement wait strategies, such as Wait.forListeningPorts() or Wait.forLogMessage(), to ensure the container is fully initialized before the test proceeds.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/github/java-testcontainers/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip java-testcontainers.zip -d .agents/skills
Documentation by jdubois
Java Testcontainers (2.x)
Run real dependencies (databases, brokers, caches) in Docker for integration tests. This skill is framework-neutral. If the project uses Spring Boot and the spring-boot-testing skill is installed, use it for @ServiceConnection, @DynamicPropertySource and test slices; for Quarkus, rely on Dev Services (Quarkus skills/docs), not hand-wired containers.
Workflow
Detect the existing version (
pom.xml/build.gradle) and test framework (JUnit Jupiter). Testcontainers 2.x has no JUnit 4 support.Pick the smallest container that matches production (same DB engine and major version; never H2 as a stand-in for behavior you care about).
Choose a lifecycle (table below), configure waiting, wire connection details into the code under test.
Run locally and in CI; check Docker availability and speed.
Testcontainers 2.x essentials
Artifacts are all prefixed
testcontainers-:org.testcontainers:testcontainers-junit-jupiter,testcontainers-postgresql,testcontainers-mysql,testcontainers-kafka, etc. (1.x:junit-jupiter,postgresql). Core staysorg.testcontainers:testcontainers.Module container classes moved to per-module packages, e.g.
org.testcontainers.postgresql.PostgreSQLContainer(1.x:org.testcontainers.containers.PostgreSQLContainer). Core classes (GenericContainer,Network,wait.strategy.*) remain underorg.testcontainers.containers.Module container classes are no longer generic:
PostgreSQLContainer, notPostgreSQLContainer<?>.Use a BOM to keep versions aligned:
org.testcontainers:testcontainers-bom:2.0.5(verify the current release on Maven Central).Docker Compose: use
ComposeContainer(Compose v2);DockerComposeContaineris still present in 2.0.5 (older API); preferComposeContainer.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>2.0.5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.14</version>
<scope>test</scope>
</dependency>
</dependencies>Gradle: testImplementation platform("org.testcontainers:testcontainers-bom:2.0.5"), then the same Testcontainers artifacts without versions, plus testRuntimeOnly "org.postgresql:postgresql:42.7.14". The Testcontainers module does not bundle the JDBC driver; skip the explicit driver when the project already depends on one (e.g. via Spring Boot's BOM).
Minimal test
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.postgresql.PostgreSQLContainer;
@Testcontainers
class CustomerStoreIT {
@Container
static final PostgreSQLContainer POSTGRES = new PostgreSQLContainer("postgres:17-alpine");
@Test
void connects() throws Exception {
try (var c = java.sql.DriverManager.getConnection(
POSTGRES.getJdbcUrl(), POSTGRES.getUsername(), POSTGRES.getPassword())) {
org.junit.jupiter.api.Assertions.assertTrue(c.isValid(2));
}
}
}Always pin the image tag (never latest); match the production major version.
Lifecycle decision
| Need | Pattern |
|---|---|
| One container per test class | static @Container field |
| Fresh container per test method (slow, strong isolation) | non-static @Container field |
| One container for the whole test run (fastest) | singleton holder, see references/lifecycle-and-reuse.md |
| Keep containers between local runs | withReuse(true) + opt-in property, local only |
Prefer sharing a container and isolating data (truncate/rollback, unique schema or DB per test) over restarting containers.
Checklist
[ ] Image tag pinned and matches production.
[ ] Wait strategy meaningful (module defaults are usually fine; custom for
GenericContainer).[ ] No fixed host ports; use
getMappedPort/getHost.[ ] Containers shared across classes where possible (singleton).
[ ] Integration tests named
*ITand run by failsafe (Maven) or a separate Gradle test task, so unit tests stay fast.[ ] No secrets or real credentials in test containers.
[ ] CI runner has Docker; log output is captured on failure.
References
references/lifecycle-and-reuse.md: per-class/per-method, singleton pattern, reuse, parallel runs, data isolation.references/containers-and-waiting.md: modules vsGenericContainer, wait strategies, env/config, init scripts, copy files, networks, multi-container setups, Docker Compose.references/environment-ci-troubleshooting.md: Docker/Podman/Colima setup, CI, private registries, performance, common errors, 1.x to 2.x migration table.
Official docs: https://java.testcontainers.org/
Upgrading to 2.x: no dedicated migration page was found on java.testcontainers.org; read the 2.0.0 release notes https://github.com/testcontainers/testcontainers-java/releases/tag/2.0.0 and all releases https://github.com/testcontainers/testcontainers-java/releases for the target version, and prefer them over this skill when they conflict.
Files
- ATTRIBUTION.mdAdded by AutoGineer869 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md5.9 KiB
- references/containers-and-waiting.md4.3 KiB
- references/environment-ci-troubleshooting.md4.7 KiB
- references/lifecycle-and-reuse.md2.9 KiB
references/containers-and-waiting.md
Containers, waiting, networks, Compose
Modules vs GenericContainer
Prefer a dedicated module (testcontainers-postgresql, -mysql, -mariadb, -kafka, -mongodb, -localstack, ...) when one exists: it supplies the right wait strategy, credentials, and typed getters. Fall back to GenericContainer for anything else.
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import java.time.Duration;
GenericContainer<?> app = new GenericContainer<>("my-registry/my-service:1.4.2")
.withExposedPorts(8080)
.withEnv("MODE", "test")
.waitingFor(Wait.forHttp("/health").forStatusCode(200))
.withStartupTimeout(Duration.ofMinutes(2));Wait strategies
Set via waitingFor(...). Pick the signal that means "actually ready", not "process started".
| Strategy | Use |
|---|---|
Wait.forListeningPorts() | Port open (may be before the app is ready) |
Wait.forHttp(path).forStatusCode(200) | HTTP health endpoints |
Wait.forLogMessage(regex, times) | Ready line in logs (regex must match the whole line, e.g. .*ready.*\\n) |
Wait.forHealthcheck() | Image defines HEALTHCHECK |
Wait.forSuccessfulCommand("cmd") | Probe via exec inside the container |
new WaitAllStrategy().withStrategy(a).withStrategy(b) | Combine strategies (org.testcontainers.containers.wait.strategy.WaitAllStrategy) |
Raise withStartupTimeout on slow CI instead of adding sleeps. Never use Thread.sleep; poll with Awaitility for asynchronous outcomes in tests.
Configuration and files
withEnv,withCommand,withLabel,withTmpFs(Map.of("/var/lib/postgresql/data", "rw"))(RAM-backed data dir speeds DB tests).Classpath resources:
withCopyFileToContainer(MountableFile.forClasspathResource("conf.yml"), "/etc/app/conf.yml")(preferred over bind mounts: works with remote Docker).Bind mounts (
withFileSystemBind) break on remote daemons and some rootless setups; avoid.
Init scripts
Database modules: withInitScript("db/init.sql") (classpath) runs once after start. Alternatively copy scripts into /docker-entrypoint-initdb.d/ (official Postgres/MySQL images). For schema evolution, run your real migration tool (Flyway/Liquibase) against the container rather than duplicating DDL in init scripts.
Networks and multi-container setups
Containers reach each other by network alias, not by mapped port.
import org.testcontainers.containers.Network;
Network net = Network.newNetwork(); // cleaned up by Ryuk
var db = new PostgreSQLContainer("postgres:17-alpine")
.withNetwork(net).withNetworkAliases("db");
var app = new GenericContainer<>("my-registry/my-service:1.4.2")
.withNetwork(net)
.withEnv("DB_URL", "jdbc:postgresql://db:5432/test")
.dependsOn(db)
.withExposedPorts(8080);Inside the network use the container-internal port (5432); from the test JVM use getMappedPort.
Docker Compose module
For stacks already described in a compose file, use ComposeContainer (requires the Docker Compose v2 CLI; class org.testcontainers.containers.ComposeContainer, in the core artifact).
import org.testcontainers.containers.ComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import java.io.File;
static ComposeContainer env = new ComposeContainer(new File("src/test/resources/compose-test.yml"))
.withExposedService("api", 8080, Wait.forHttp("/health").forStatusCode(200))
.withExposedService("db", 5432);
env.start(); // endpoints are only available after start
try {
String host = env.getServiceHost("api", 8080);
Integer port = env.getServicePort("api", 8080);
// run the test against host:port
} finally {
env.stop();
}Do not publish fixed host ports in the compose file used for tests. Prefer individual containers for one or two services (faster, typed); use Compose to reuse a multi-service definition.
Useful extras
container.followOutput(new Slf4jLogConsumer(log))to stream logs;getLogs()on failure.withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(512L * 1024 * 1024))to cap resources.DockerImageName.parse("mirror/postgres:17").asCompatibleSubstituteFor("postgres")for mirrored or custom images with a module.
Requirements and compatibility
Written for: GitHub Copilot
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- First committed by
jdubois
- Publisher
- GitHub (Official)
- Distributed by
- GitHub
- Source
- github/awesome-copilot · skills/java-testcontainers · commit 797337f
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Contributors
- jdubois
AutoGineer is not affiliated with or endorsed by jdubois or GitHub.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download java-testcontainers.zip