diff --git a/content/guides/testcontainers-java-aws-localstack.md b/content/guides/testcontainers-java-aws-localstack.md
deleted file mode 100644
index fa6295b5ee..0000000000
--- a/content/guides/testcontainers-java-aws-localstack.md
+++ /dev/null
@@ -1,450 +0,0 @@
----
-title: Testing AWS service integrations using LocalStack
-linkTitle: AWS LocalStack
-description: Learn how to test Spring Cloud AWS applications using LocalStack and Testcontainers.
-keywords: testcontainers, java, spring boot, testing, aws, localstack, s3, sqs, spring cloud aws
-summary: |
- Learn how to create a Spring Boot application with Spring Cloud AWS,
- then test S3 and SQS integrations using Testcontainers and LocalStack.
-aliases:
- - /guides/testcontainers-java-aws-localstack/create-project/
- - /guides/testcontainers-java-aws-localstack/run-tests/
- - /guides/testcontainers-java-aws-localstack/write-tests/
-params:
- tags: [testing]
- time: 25 minutes
----
-
-
-
-
-In this guide, you will learn how to:
-
-- Create a Spring Boot application with Spring Cloud AWS integration
-- Use AWS S3 and SQS services
-- Test the application using Testcontainers and LocalStack
-
-## Prerequisites
-
-- Java 17+
-- Maven or Gradle
-- A Docker environment supported by Testcontainers
-
-> [!NOTE]
-> If you're new to Testcontainers, visit the
-> [Testcontainers overview](https://testcontainers.com/getting-started/) to learn more about
-> Testcontainers and the benefits of using it.
-
-## Create the Spring Boot project
-
-### Set up the project
-
-Create a Spring Boot project from [Spring Initializr](https://start.spring.io)
-by selecting the **Testcontainers** starter. Spring Cloud AWS starters are not
-available on Spring Initializr, so you need to add them manually.
-
-Alternatively, clone the
-[guide repository](https://github.com/testcontainers/tc-guide-testing-aws-service-integrations-using-localstack).
-
-Add the Spring Cloud AWS BOM to your dependency management and add the S3, SQS
-starters as dependencies. Testcontainers provides a
-[LocalStack module](https://testcontainers.com/modules/localstack/) for testing
-AWS service integrations. You also need
-[Awaitility](http://www.awaitility.org/) for testing asynchronous SQS
-processing.
-
-The key dependencies in `pom.xml` are:
-
-```xml
-
- 17
- 2.0.4
- 3.0.3
-
-
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- io.awspring.cloud
- spring-cloud-aws-starter-s3
-
-
- io.awspring.cloud
- spring-cloud-aws-starter-sqs
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.boot
- spring-boot-testcontainers
- test
-
-
- org.testcontainers
- testcontainers-junit-jupiter
- test
-
-
- org.testcontainers
- testcontainers-localstack
- test
-
-
- org.awaitility
- awaitility
- test
-
-
-
-
-
-
- io.awspring.cloud
- spring-cloud-aws-dependencies
- ${awspring.version}
- pom
- import
-
-
-
-```
-
-### Create the configuration properties
-
-To make the SQS queue and S3 bucket names configurable, create an
-`ApplicationProperties` record:
-
-```java
-package com.testcontainers.demo;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@ConfigurationProperties(prefix = "app")
-public record ApplicationProperties(String queue, String bucket) {}
-```
-
-Then add `@ConfigurationPropertiesScan` to the main application class so that
-Spring automatically scans for `@ConfigurationProperties`-annotated classes and
-registers them as beans:
-
-```java
-package com.testcontainers.demo;
-
-import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
-
-@SpringBootApplication
-@ConfigurationPropertiesScan
-public class Application {
-
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
-}
-```
-
-### Implement StorageService for S3
-
-Spring Cloud AWS provides higher-level abstractions like `S3Template` with
-convenience methods for uploading and downloading files. Create a
-`StorageService` class:
-
-```java
-package com.testcontainers.demo;
-
-import io.awspring.cloud.s3.S3Template;
-import java.io.IOException;
-import java.io.InputStream;
-import org.springframework.stereotype.Service;
-
-@Service
-public class StorageService {
-
- private final S3Template s3Template;
-
- public StorageService(S3Template s3Template) {
- this.s3Template = s3Template;
- }
-
- public void upload(String bucketName, String key, InputStream stream) {
- this.s3Template.upload(bucketName, key, stream);
- }
-
- public InputStream download(String bucketName, String key)
- throws IOException {
- return this.s3Template.download(bucketName, key).getInputStream();
- }
-
- public String downloadAsString(String bucketName, String key)
- throws IOException {
- try (InputStream is = this.download(bucketName, key)) {
- return new String(is.readAllBytes());
- }
- }
-}
-```
-
-### Create the SQS message model
-
-Create a `Message` record that represents the payload you send to the SQS
-queue:
-
-```java
-package com.testcontainers.demo;
-
-import java.util.UUID;
-
-public record Message(UUID uuid, String content) {}
-```
-
-### Implement the message sender
-
-Create `MessageSender`, which uses `SqsTemplate` to publish messages:
-
-```java
-package com.testcontainers.demo;
-
-import io.awspring.cloud.sqs.operations.SqsTemplate;
-import org.springframework.stereotype.Service;
-
-@Service
-public class MessageSender {
-
- private final SqsTemplate sqsTemplate;
-
- public MessageSender(SqsTemplate sqsTemplate) {
- this.sqsTemplate = sqsTemplate;
- }
-
- public void publish(String queueName, Message message) {
- sqsTemplate.send(to -> to.queue(queueName).payload(message));
- }
-}
-```
-
-### Implement the message listener
-
-Create `MessageListener` with a handler method annotated with `@SqsListener`.
-When a message arrives, the listener uploads the content to an S3 bucket using
-the message UUID as the key:
-
-```java
-package com.testcontainers.demo;
-
-import io.awspring.cloud.sqs.annotation.SqsListener;
-import java.io.ByteArrayInputStream;
-import java.nio.charset.StandardCharsets;
-import org.springframework.stereotype.Service;
-
-@Service
-public class MessageListener {
-
- private final StorageService storageService;
- private final ApplicationProperties properties;
-
- public MessageListener(
- StorageService storageService,
- ApplicationProperties properties
- ) {
- this.storageService = storageService;
- this.properties = properties;
- }
-
- @SqsListener(queueNames = { "${app.queue}" })
- public void handle(Message message) {
- String bucketName = this.properties.bucket();
- String key = message.uuid().toString();
- ByteArrayInputStream is = new ByteArrayInputStream(
- message.content().getBytes(StandardCharsets.UTF_8)
- );
- this.storageService.upload(bucketName, key, is);
- }
-}
-```
-
-The `${app.queue}` expression reads the queue name from application
-configuration instead of hard-coding it.
-
-## Write tests with Testcontainers
-
-To test the application, you need a running LocalStack instance that emulates
-the AWS S3 and SQS services. Testcontainers spins up LocalStack in a Docker
-container and `@DynamicPropertySource` connects it to Spring Cloud AWS.
-
-### Configure the test container
-
-You can start a LocalStack container and configure the Spring Cloud AWS
-properties to talk to it instead of actual AWS services. The properties you
-need to set are:
-
-```properties
-spring.cloud.aws.s3.endpoint=http://localhost:4566
-spring.cloud.aws.sqs.endpoint=http://localhost:4566
-spring.cloud.aws.credentials.access-key=noop
-spring.cloud.aws.credentials.secret-key=noop
-spring.cloud.aws.region.static=us-east-1
-```
-
-For testing, use an ephemeral container that starts on a random available port
-so that you can run multiple builds in CI in parallel without port conflicts.
-
-### Write the test
-
-Create `MessageListenerTest.java`:
-
-```java
-package com.testcontainers.demo;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.awaitility.Awaitility.await;
-import static org.testcontainers.containers.localstack.LocalStackContainer.Service.S3;
-import static org.testcontainers.containers.localstack.LocalStackContainer.Service.SQS;
-
-import java.io.IOException;
-import java.time.Duration;
-import java.util.UUID;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
-import org.testcontainers.containers.localstack.LocalStackContainer;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
-import org.testcontainers.utility.DockerImageName;
-
-@SpringBootTest
-@Testcontainers
-class MessageListenerTest {
-
- @Container
- static LocalStackContainer localStack = new LocalStackContainer(
- DockerImageName.parse("localstack/localstack:3.0")
- );
-
- static final String BUCKET_NAME = UUID.randomUUID().toString();
- static final String QUEUE_NAME = UUID.randomUUID().toString();
-
- @DynamicPropertySource
- static void overrideProperties(DynamicPropertyRegistry registry) {
- registry.add("app.bucket", () -> BUCKET_NAME);
- registry.add("app.queue", () -> QUEUE_NAME);
- registry.add(
- "spring.cloud.aws.region.static",
- () -> localStack.getRegion()
- );
- registry.add(
- "spring.cloud.aws.credentials.access-key",
- () -> localStack.getAccessKey()
- );
- registry.add(
- "spring.cloud.aws.credentials.secret-key",
- () -> localStack.getSecretKey()
- );
- registry.add(
- "spring.cloud.aws.s3.endpoint",
- () -> localStack.getEndpointOverride(S3).toString()
- );
- registry.add(
- "spring.cloud.aws.sqs.endpoint",
- () -> localStack.getEndpointOverride(SQS).toString()
- );
- }
-
- @BeforeAll
- static void beforeAll() throws IOException, InterruptedException {
- localStack.execInContainer("awslocal", "s3", "mb", "s3://" + BUCKET_NAME);
- localStack.execInContainer(
- "awslocal",
- "sqs",
- "create-queue",
- "--queue-name",
- QUEUE_NAME
- );
- }
-
- @Autowired
- StorageService storageService;
-
- @Autowired
- MessageSender publisher;
-
- @Autowired
- ApplicationProperties properties;
-
- @Test
- void shouldHandleMessageSuccessfully() {
- Message message = new Message(UUID.randomUUID(), "Hello World");
- publisher.publish(properties.queue(), message);
-
- await()
- .pollInterval(Duration.ofSeconds(2))
- .atMost(Duration.ofSeconds(10))
- .ignoreExceptions()
- .untilAsserted(() -> {
- String msg = storageService.downloadAsString(
- properties.bucket(),
- message.uuid().toString()
- );
- assertThat(msg).isEqualTo("Hello World");
- });
- }
-}
-```
-
-Here's what the test does:
-
-- `@SpringBootTest` starts the full Spring application context.
-- The Testcontainers JUnit 5 annotations `@Testcontainers` and `@Container`
- manage the lifecycle of a `LocalStackContainer` instance.
-- `@DynamicPropertySource` obtains the dynamic S3 and SQS endpoint URLs,
- region, access key, and secret key from the container, and registers them as
- Spring Cloud AWS configuration properties.
-- `@BeforeAll` creates the required SQS queue and S3 bucket using the
- `awslocal` CLI tool that comes pre-installed in the LocalStack Docker image.
- The `localStack.execInContainer()` API runs commands inside the container.
-- `shouldHandleMessageSuccessfully()` publishes a `Message` to the SQS queue.
- The listener receives the message and stores its content in the S3 bucket
- with the UUID as the key. Awaitility waits up to 10 seconds for the expected
- content to appear in the bucket.
-
-## Run tests and next steps
-
-### Run the tests
-
-```console
-$ ./mvnw test
-```
-
-Or with Gradle:
-
-```console
-$ ./gradlew test
-```
-
-You should see the LocalStack Docker container start and the test pass. After
-the tests finish, the container stops and is removed automatically.
-
-### Summary
-
-LocalStack lets you develop and test AWS-based applications locally.
-The Testcontainers LocalStack module makes it straightforward to write
-integration tests by using ephemeral LocalStack containers that start on random
-ports with no external setup required.
-
-To learn more about Testcontainers, visit the
-[Testcontainers overview](https://testcontainers.com/getting-started/).
-
-### Further reading
-
-- [Testcontainers LocalStack module](https://java.testcontainers.org/modules/localstack/)
-- [Getting started with Testcontainers for Java](https://java.testcontainers.org/quickstart/junit_5_quickstart/)
-- [Spring Cloud AWS documentation](https://docs.awspring.io/spring-cloud-aws/docs/3.0.3/reference/html/index.html)
diff --git a/content/guides/testcontainers-java-jooq-flyway.md b/content/guides/testcontainers-java-jooq-flyway.md
deleted file mode 100644
index e6a3a954eb..0000000000
--- a/content/guides/testcontainers-java-jooq-flyway.md
+++ /dev/null
@@ -1,610 +0,0 @@
----
-title: Working with jOOQ and Flyway using Testcontainers
-linkTitle: jOOQ and Flyway
-description: Learn how to generate jOOQ code from a database using Testcontainers and Flyway, then test your persistence layer.
-keywords: testcontainers, java, testing, jooq, flyway, postgresql, spring boot, code generation
-summary: |
- Generate typesafe jOOQ code from a real PostgreSQL database managed by
- Flyway migrations, then test repositories using Testcontainers.
-aliases:
- - /guides/testcontainers-java-jooq-flyway/create-project/
- - /guides/testcontainers-java-jooq-flyway/run-tests/
- - /guides/testcontainers-java-jooq-flyway/write-tests/
-params:
- tags: [testing]
- time: 25 minutes
----
-
-
-
-
-In this guide, you will learn how to:
-
-- Create a Spring Boot application with jOOQ support
-- Generate jOOQ code using Testcontainers, Flyway, and a Maven plugin
-- Implement basic database operations using jOOQ
-- Load complex object graphs using jOOQ's MULTISET feature
-- Test the jOOQ persistence layer using Testcontainers
-
-## Prerequisites
-
-- Java 17+
-- Maven
-- A Docker environment supported by Testcontainers
-
-> [!NOTE]
-> If you're new to Testcontainers, visit the
-> [Testcontainers overview](https://testcontainers.com/getting-started/) to learn more about
-> Testcontainers and the benefits of using it.
-
-## Create the Spring Boot project
-
-### Set up the project
-
-Create a Spring Boot project from [Spring Initializr](https://start.spring.io)
-by selecting Maven as the build tool and adding the **JOOQ Access Layer**,
-**Flyway Migration**, **Spring Boot DevTools**, **PostgreSQL Driver**, and
-**Testcontainers** starters.
-
-Alternatively, clone the
-[guide repository](https://github.com/testcontainers/tc-guide-working-with-jooq-flyway-using-testcontainers).
-
-jOOQ (jOOQ Object Oriented Querying) provides a fluent API for building
-typesafe SQL queries. To get the full benefit of its typesafe DSL, you need
-to generate Java code from your database tables, views, and other objects.
-
-> [!TIP]
-> To learn more about how the jOOQ code generator helps, read
-> [Why You Should Use jOOQ With Code Generation](https://blog.jooq.org/why-you-should-use-jooq-with-code-generation/).
-
-The typical process for building and testing the application with jOOQ code
-generation is:
-
-1. Create a database instance using Testcontainers.
-2. Apply Flyway database migrations.
-3. Run the jOOQ code generator to produce Java code from the database objects.
-4. Run integration tests.
-
-The
-[testcontainers-jooq-codegen-maven-plugin](https://github.com/testcontainers/testcontainers-jooq-codegen-maven-plugin)
-automates this as part of the Maven build.
-
-### Create Flyway migration scripts
-
-The sample application has `users`, `posts`, and `comments` tables. Create
-the first migration script following the Flyway naming convention.
-
-Create `src/main/resources/db/migration/V1__create_tables.sql`:
-
-```sql
-create table users
-(
- id bigserial not null,
- name varchar not null,
- email varchar not null,
- created_at timestamp,
- updated_at timestamp,
- primary key (id),
- constraint user_email_unique unique (email)
-);
-
-create table posts
-(
- id bigserial not null,
- title varchar not null,
- content varchar not null,
- created_by bigint references users (id) not null,
- created_at timestamp,
- updated_at timestamp,
- primary key (id)
-);
-
-create table comments
-(
- id bigserial not null,
- name varchar not null,
- content varchar not null,
- post_id bigint references posts (id) not null,
- created_at timestamp,
- updated_at timestamp,
- primary key (id)
-);
-
-ALTER SEQUENCE users_id_seq RESTART WITH 101;
-ALTER SEQUENCE posts_id_seq RESTART WITH 101;
-ALTER SEQUENCE comments_id_seq RESTART WITH 101;
-```
-
-The sequence values restart at 101 so that you can insert sample data with
-explicit primary key values for testing.
-
-### Configure jOOQ code generation
-
-Add the `testcontainers-jooq-codegen-maven-plugin` to `pom.xml`:
-
-```xml
-
- 2.0.4
- 0.0.4
-
-
-
-
-
- org.testcontainers
- testcontainers-jooq-codegen-maven-plugin
- ${testcontainers-jooq-codegen-maven-plugin.version}
-
-
- org.testcontainers
- testcontainers-postgresql
- ${testcontainers.version}
-
-
- org.postgresql
- postgresql
- ${postgresql.version}
-
-
-
-
- generate-jooq-sources
-
- generate
-
- generate-sources
-
-
- POSTGRES
- postgres:16-alpine
-
-
-
- filesystem:src/main/resources/db/migration
-
-
-
-
-
- .*
- flyway_schema_history
- public
-
-
- com.testcontainers.demo.jooq
- target/generated-sources/jooq
-
-
-
-
-
-
-
-
-
-```
-
-Here's what the plugin configuration does:
-
-- The `/` section sets the database type to
- `POSTGRES` and the Docker image to `postgres:16-alpine`.
-- The `/` section points to the Flyway migration
- scripts.
-- The `/` section configures the package name and
- output directory for the generated code. You can use any configuration
- option that the official `jooq-code-generator` plugin supports.
-
-When you run `./mvnw clean package`, the plugin uses Testcontainers to
-spin up a PostgreSQL container, applies the Flyway migrations, and generates
-Java code under `target/generated-sources/jooq`.
-
-### Create model classes
-
-Create model classes to represent the data structures for various use cases.
-These records hold a subset of column values from the tables.
-
-`User.java`:
-
-```java
-package com.testcontainers.demo.domain;
-
-public record User(Long id, String name, String email) {}
-```
-
-`Post.java`:
-
-```java
-package com.testcontainers.demo.domain;
-
-import java.time.LocalDateTime;
-import java.util.List;
-
-public record Post(
- Long id,
- String title,
- String content,
- User createdBy,
- List comments,
- LocalDateTime createdAt,
- LocalDateTime updatedAt
-) {}
-```
-
-`Comment.java`:
-
-```java
-package com.testcontainers.demo.domain;
-
-import java.time.LocalDateTime;
-
-public record Comment(
- Long id,
- String name,
- String content,
- LocalDateTime createdAt,
- LocalDateTime updatedAt
-) {}
-```
-
-### Implement repositories using jOOQ
-
-Create `UserRepository.java` with methods to create a user and look up a user
-by email:
-
-```java
-package com.testcontainers.demo.domain;
-
-import static com.testcontainers.demo.jooq.tables.Users.USERS;
-import static org.jooq.Records.mapping;
-
-import java.time.LocalDateTime;
-import java.util.Optional;
-import org.jooq.DSLContext;
-import org.springframework.stereotype.Repository;
-
-@Repository
-class UserRepository {
-
- private final DSLContext dsl;
-
- UserRepository(DSLContext dsl) {
- this.dsl = dsl;
- }
-
- public User createUser(User user) {
- return this.dsl.insertInto(USERS)
- .set(USERS.NAME, user.name())
- .set(USERS.EMAIL, user.email())
- .set(USERS.CREATED_AT, LocalDateTime.now())
- .returningResult(USERS.ID, USERS.NAME, USERS.EMAIL)
- .fetchOne(mapping(User::new));
- }
-
- public Optional getUserByEmail(String email) {
- return this.dsl.select(USERS.ID, USERS.NAME, USERS.EMAIL)
- .from(USERS)
- .where(USERS.EMAIL.equalIgnoreCase(email))
- .fetchOptional(mapping(User::new));
- }
-}
-```
-
-The jOOQ DSL looks similar to SQL but written in Java. Because the code is
-generated from the database schema, it stays in sync with the database
-structure and provides type safety. For example,
-`where(USERS.EMAIL.equalIgnoreCase(email))` expects a `String` value. If you
-pass a non-string value like `123`, you get a compiler error.
-
-### Fetch complex object graphs
-
-jOOQ shines when it comes to complex queries. The database has a many-to-one
-relationship from `Post` to `User` and a one-to-many relationship from `Post`
-to `Comment`.
-
-Create `PostRepository.java` to load a `Post` with its creator and comments
-using a single query with jOOQ's MULTISET feature:
-
-```java
-package com.testcontainers.demo.domain;
-
-import static com.testcontainers.demo.jooq.Tables.COMMENTS;
-import static com.testcontainers.demo.jooq.tables.Posts.POSTS;
-import static org.jooq.Records.mapping;
-import static org.jooq.impl.DSL.multiset;
-import static org.jooq.impl.DSL.row;
-import static org.jooq.impl.DSL.select;
-
-import java.util.Optional;
-import org.jooq.DSLContext;
-import org.springframework.stereotype.Repository;
-
-@Repository
-class PostRepository {
-
- private final DSLContext dsl;
-
- PostRepository(DSLContext dsl) {
- this.dsl = dsl;
- }
-
- public Optional getPostById(Long id) {
- return this.dsl.select(
- POSTS.ID,
- POSTS.TITLE,
- POSTS.CONTENT,
- row(POSTS.users().ID, POSTS.users().NAME, POSTS.users().EMAIL)
- .mapping(User::new)
- .as("createdBy"),
- multiset(
- select(
- COMMENTS.ID,
- COMMENTS.NAME,
- COMMENTS.CONTENT,
- COMMENTS.CREATED_AT,
- COMMENTS.UPDATED_AT
- )
- .from(COMMENTS)
- .where(POSTS.ID.eq(COMMENTS.POST_ID))
- )
- .as("comments")
- .convertFrom(r -> r.map(mapping(Comment::new))),
- POSTS.CREATED_AT,
- POSTS.UPDATED_AT
- )
- .from(POSTS)
- .where(POSTS.ID.eq(id))
- .fetchOptional(mapping(Post::new));
- }
-}
-```
-
-This uses jOOQ's
-[nested records](https://www.jooq.org/doc/latest/manual/sql-building/column-expressions/nested-records/)
-for the many-to-one `Post`-to-`User` association and
-[MULTISET](https://www.jooq.org/doc/latest/manual/sql-building/column-expressions/multiset-value-constructor/)
-for the one-to-many `Post`-to-`Comment` association.
-
-## Write tests with Testcontainers
-
-Before writing the tests, create an SQL script to seed test data at
-`src/test/resources/test-data.sql`:
-
-```sql
-DELETE FROM comments;
-DELETE FROM posts;
-DELETE FROM users;
-
-INSERT INTO users(id, name, email) VALUES
-(1, 'Siva', 'siva@gmail.com'),
-(2, 'Oleg', 'oleg@gmail.com');
-
-INSERT INTO posts(id, title, content, created_by, created_at) VALUES
-(1, 'Post 1 Title', 'Post 1 content', 1, CURRENT_TIMESTAMP),
-(2, 'Post 2 Title', 'Post 2 content', 2, CURRENT_TIMESTAMP);
-
-INSERT INTO comments(id, name, content, post_id, created_at) VALUES
-(1, 'Ron', 'Comment 1', 1, CURRENT_TIMESTAMP),
-(2, 'James', 'Comment 2', 1, CURRENT_TIMESTAMP),
-(3, 'Robert', 'Comment 3', 2, CURRENT_TIMESTAMP);
-```
-
-### Test with the @JooqTest slice
-
-The `@JooqTest` annotation loads only the persistence layer components and
-auto-configures jOOQ's `DSLContext`. Use the Testcontainers special JDBC URL
-to start a Postgres container.
-
-Create `UserRepositoryJooqTest.java`:
-
-```java
-package com.testcontainers.demo.domain;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.jooq.DSLContext;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.autoconfigure.jooq.JooqTest;
-import org.springframework.test.context.jdbc.Sql;
-
-@JooqTest(
- properties = {
- "spring.test.database.replace=none",
- "spring.datasource.url=jdbc:tc:postgresql:16-alpine:///db",
- }
-)
-@Sql("/test-data.sql")
-class UserRepositoryJooqTest {
-
- @Autowired
- DSLContext dsl;
-
- UserRepository repository;
-
- @BeforeEach
- void setUp() {
- this.repository = new UserRepository(dsl);
- }
-
- @Test
- void shouldCreateUserSuccessfully() {
- User user = new User(null, "John", "john@gmail.com");
-
- User savedUser = repository.createUser(user);
-
- assertThat(savedUser.id()).isNotNull();
- assertThat(savedUser.name()).isEqualTo("John");
- assertThat(savedUser.email()).isEqualTo("john@gmail.com");
- }
-
- @Test
- void shouldGetUserByEmail() {
- User user = repository.getUserByEmail("siva@gmail.com").orElseThrow();
-
- assertThat(user.id()).isEqualTo(1L);
- assertThat(user.name()).isEqualTo("Siva");
- assertThat(user.email()).isEqualTo("siva@gmail.com");
- }
-}
-```
-
-Here's what the test does:
-
-- `@JooqTest` loads only the persistence layer and auto-configures
- `DSLContext`.
-- The Testcontainers special JDBC URL
- (`jdbc:tc:postgresql:16-alpine:///db`) starts a PostgreSQL container
- automatically.
-- Because `flyway-core` is on the classpath, Spring Boot runs the Flyway
- migrations from `src/main/resources/db/migration` on startup.
-- `@Sql("/test-data.sql")` loads the test data before each test.
-- The `UserRepository` is instantiated manually with the injected
- `DSLContext`.
-
-### Integration test with @SpringBootTest
-
-For a full integration test, use `@SpringBootTest` with the Testcontainers
-`@ServiceConnection` support introduced in Spring Boot 3.1.
-
-Create `UserRepositoryTest.java`:
-
-```java
-package com.testcontainers.demo.domain;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
-import org.springframework.test.context.jdbc.Sql;
-import org.testcontainers.postgresql.PostgreSQLContainer;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
-
-@SpringBootTest
-@Sql("/test-data.sql")
-@Testcontainers
-class UserRepositoryTest {
-
- @Container
- @ServiceConnection
- static PostgreSQLContainer postgres = new PostgreSQLContainer(
- "postgres:16-alpine"
- );
-
- @Autowired
- UserRepository repository;
-
- @Test
- void shouldCreateUserSuccessfully() {
- User user = new User(null, "John", "john@gmail.com");
-
- User savedUser = repository.createUser(user);
-
- assertThat(savedUser.id()).isNotNull();
- assertThat(savedUser.name()).isEqualTo("John");
- assertThat(savedUser.email()).isEqualTo("john@gmail.com");
- }
-
- @Test
- void shouldGetUserByEmail() {
- User user = repository.getUserByEmail("siva@gmail.com").orElseThrow();
-
- assertThat(user.id()).isEqualTo(1L);
- assertThat(user.name()).isEqualTo("Siva");
- assertThat(user.email()).isEqualTo("siva@gmail.com");
- }
-}
-```
-
-Here's what the test does:
-
-- `@SpringBootTest` loads the entire application context, so
- `UserRepository` is injected directly.
-- `@Testcontainers` and `@Container` manage the PostgreSQL container
- lifecycle.
-- `@ServiceConnection` auto-configures the datasource properties from the
- running container, replacing the need for `@DynamicPropertySource`.
-- `@Sql("/test-data.sql")` initializes the test data.
-
-### Test PostRepository
-
-Test the `PostRepository` that fetches complex object graphs using the
-Testcontainers special JDBC URL.
-
-Create `PostRepositoryTest.java`:
-
-```java
-package com.testcontainers.demo.domain;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.jdbc.Sql;
-
-@SpringBootTest(
- properties = {
- "spring.test.database.replace=none",
- "spring.datasource.url=jdbc:tc:postgresql:16-alpine:///db",
- }
-)
-@Sql("/test-data.sql")
-class PostRepositoryTest {
-
- @Autowired
- PostRepository repository;
-
- @Test
- void shouldGetPostById() {
- Post post = repository.getPostById(1L).orElseThrow();
-
- assertThat(post.id()).isEqualTo(1L);
- assertThat(post.title()).isEqualTo("Post 1 Title");
- assertThat(post.content()).isEqualTo("Post 1 content");
- assertThat(post.createdBy().id()).isEqualTo(1L);
- assertThat(post.createdBy().name()).isEqualTo("Siva");
- assertThat(post.createdBy().email()).isEqualTo("siva@gmail.com");
- assertThat(post.comments()).hasSize(2);
- }
-}
-```
-
-This test verifies that `getPostById` loads the post along with its creator
-and comments in a single query using jOOQ's MULTISET feature.
-
-## Run tests and next steps
-
-### Run the tests
-
-```console
-$ ./mvnw test
-```
-
-You should see the PostgreSQL Docker container start, jOOQ code generation
-complete, and all tests pass. After the tests finish, the container stops and
-is removed automatically.
-
-### Summary
-
-The Testcontainers library helps you generate Java code from the database
-using the jOOQ code generator and test your persistence layer against the
-same type of database (PostgreSQL) that you use in production, instead of
-mocks or in-memory databases.
-
-Because the code is always generated from the database's current state, you
-can be confident that your code stays in sync with database changes. You're
-free to refactor and still verify that the application works as expected.
-
-To learn more about Testcontainers, visit the
-[Testcontainers overview](https://testcontainers.com/getting-started/).
-
-### Further reading
-
-- [jOOQ documentation](https://www.jooq.org/)
-- [jOOQ code generation](https://www.jooq.org/doc/latest/manual/code-generation/)
-- [Spring Boot Testcontainers support](https://docs.spring.io/spring-boot/reference/testing/testcontainers.html)
-- [Replace H2 with a real database for testing](/guides/testcontainers-java-replace-h2/)
diff --git a/content/guides/testcontainers-java-keycloak-spring-boot.md b/content/guides/testcontainers-java-keycloak-spring-boot.md
deleted file mode 100644
index 187f024cd3..0000000000
--- a/content/guides/testcontainers-java-keycloak-spring-boot.md
+++ /dev/null
@@ -1,605 +0,0 @@
----
-title: Securing Spring Boot microservice using Keycloak and Testcontainers
-linkTitle: Keycloak with Spring Boot
-description: Learn how to secure a Spring Boot microservice using Keycloak and test it with the Testcontainers Keycloak module.
-keywords: testcontainers, java, spring boot, testing, keycloak, security, oauth2, jwt
-summary: |
- Learn how to create an OAuth 2.0 Resource Server using Spring Boot, secure API
- endpoints with Keycloak, and test the application using the Testcontainers Keycloak module.
-aliases:
- - /guides/testcontainers-java-keycloak-spring-boot/create-project/
- - /guides/testcontainers-java-keycloak-spring-boot/run-tests/
- - /guides/testcontainers-java-keycloak-spring-boot/write-tests/
-params:
- tags: [testing]
- time: 30 minutes
----
-
-
-
-
-In this guide, you'll learn how to:
-
-- Create an OAuth 2.0 Resource Server using Spring Boot
-- Secure API endpoints using Keycloak
-- Test the APIs using the Testcontainers Keycloak module
-- Run the application locally using the Testcontainers Keycloak module
-
-## Prerequisites
-
-- Java 17+
-- Maven or Gradle
-- A Docker environment supported by Testcontainers
-
-> [!NOTE]
-> If you're new to Testcontainers, visit the
-> [Testcontainers overview](https://testcontainers.com/getting-started/) to learn more about
-> Testcontainers and the benefits of using it.
-
-## Create the Spring Boot project
-
-### Set up the project
-
-Create a Spring Boot project from [Spring Initializr](https://start.spring.io)
-by selecting the **Spring Web**, **Validation**, **JDBC API**,
-**PostgreSQL Driver**, **Spring Security**, **OAuth2 Resource Server**, and
-**Testcontainers** starters.
-
-Alternatively, clone the
-[guide repository](https://github.com/testcontainers/tc-guide-securing-spring-boot-microservice-using-keycloak-and-testcontainers).
-
-After generating the application, add the
-[testcontainers-keycloak](https://github.com/dasniko/testcontainers-keycloak)
-community module and [REST Assured](https://rest-assured.io/) as test
-dependencies.
-
-The key dependencies in `pom.xml` are:
-
-```xml
-
- 17
- 2.0.4
-
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
-
- org.springframework.boot
- spring-boot-starter-jdbc
-
-
- org.postgresql
- postgresql
- runtime
-
-
- org.springframework.boot
- spring-boot-starter-security
-
-
- org.springframework.boot
- spring-boot-starter-oauth2-resource-server
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.security
- spring-security-test
- test
-
-
- org.springframework.boot
- spring-boot-testcontainers
- test
-
-
- org.testcontainers
- testcontainers-junit-jupiter
- test
-
-
- org.testcontainers
- testcontainers-postgresql
- test
-
-
- com.github.dasniko
- testcontainers-keycloak
- 3.4.0
- test
-
-
- io.rest-assured
- rest-assured
- test
-
-
-```
-
-### Create the domain model
-
-Create a `Product` record that represents the domain object:
-
-```java
-package com.testcontainers.products.domain;
-
-import jakarta.validation.constraints.NotEmpty;
-
-public record Product(Long id, @NotEmpty String title, String description) {}
-```
-
-### Create the repository
-
-Implement `ProductRepository` using Spring `JdbcClient` to interact with a
-PostgreSQL database:
-
-```java
-package com.testcontainers.products.domain;
-
-import java.util.List;
-import org.springframework.jdbc.core.simple.JdbcClient;
-import org.springframework.jdbc.support.GeneratedKeyHolder;
-import org.springframework.jdbc.support.KeyHolder;
-import org.springframework.stereotype.Repository;
-
-@Repository
-public class ProductRepository {
-
- private final JdbcClient jdbcClient;
-
- public ProductRepository(JdbcClient jdbcClient) {
- this.jdbcClient = jdbcClient;
- }
-
- public List getAll() {
- return jdbcClient.sql("SELECT * FROM products").query(Product.class).list();
- }
-
- public Product create(Product product) {
- String sql =
- "INSERT INTO products(title, description) VALUES (:title,:description) RETURNING id";
- KeyHolder keyHolder = new GeneratedKeyHolder();
- jdbcClient
- .sql(sql)
- .param("title", product.title())
- .param("description", product.description())
- .update(keyHolder);
- Long id = keyHolder.getKeyAs(Long.class);
- return new Product(id, product.title(), product.description());
- }
-}
-```
-
-### Add a schema creation script
-
-Create `src/main/resources/schema.sql` to initialize the `products` table:
-
-```sql
-CREATE TABLE products (
- id bigserial primary key,
- title varchar not null,
- description text
-);
-```
-
-Enable schema initialization in `src/main/resources/application.properties`:
-
-```properties
-spring.sql.init.mode=always
-```
-
-For production applications, use a database migration tool like Flyway or
-Liquibase instead.
-
-### Implement the API endpoints
-
-Create `ProductController` with endpoints to fetch all products and create a
-product:
-
-```java
-package com.testcontainers.products.api;
-
-import com.testcontainers.products.domain.Product;
-import com.testcontainers.products.domain.ProductRepository;
-import jakarta.validation.Valid;
-import java.util.List;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseStatus;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("/api/products")
-class ProductController {
-
- private final ProductRepository productRepository;
-
- ProductController(ProductRepository productRepository) {
- this.productRepository = productRepository;
- }
-
- @GetMapping
- List getAll() {
- return productRepository.getAll();
- }
-
- @PostMapping
- @ResponseStatus(HttpStatus.CREATED)
- Product createProduct(@RequestBody @Valid Product product) {
- return productRepository.create(product);
- }
-}
-```
-
-### Configure OAuth 2.0 security
-
-Create a `SecurityConfig` class that protects the API endpoints using JWT
-token-based authentication:
-
-```java
-package com.testcontainers.products.config;
-
-import static org.springframework.security.config.Customizer.withDefaults;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.http.HttpMethod;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.annotation.web.configurers.CorsConfigurer;
-import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
-import org.springframework.security.config.http.SessionCreationPolicy;
-import org.springframework.security.web.SecurityFilterChain;
-
-@Configuration
-@EnableWebSecurity
-class SecurityConfig {
-
- @Bean
- SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
- http
- .authorizeHttpRequests(c ->
- c
- .requestMatchers(HttpMethod.GET, "/api/products")
- .permitAll()
- .requestMatchers(HttpMethod.POST, "/api/products")
- .authenticated()
- .anyRequest()
- .authenticated()
- )
- .sessionManagement(c ->
- c.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
- )
- .cors(CorsConfigurer::disable)
- .csrf(CsrfConfigurer::disable)
- .oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()));
- return http.build();
- }
-}
-```
-
-This configuration:
-
-- Permits unauthenticated access to `GET /api/products`.
-- Requires authentication for `POST /api/products` and all other endpoints.
-- Configures the OAuth 2.0 Resource Server with JWT token-based authentication.
-- Disables CORS and CSRF because this is a stateless API.
-
-Add the JWT issuer URI to `application.properties`:
-
-```properties
-spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:9090/realms/keycloaktcdemo
-```
-
-### Export the Keycloak realm configuration
-
-Before writing the tests, export a Keycloak realm configuration so that the test
-environment can import it automatically. Start a temporary Keycloak instance:
-
-```console
-$ docker run -p 9090:8080 \
- -e KEYCLOAK_ADMIN=admin \
- -e KEYCLOAK_ADMIN_PASSWORD=admin \
- quay.io/keycloak/keycloak:25 start-dev
-```
-
-Open `http://localhost:9090` and sign in to the Admin Console with `admin/admin`.
-Then set up the realm:
-
-1. In the top-left corner, select the realm drop-down and create a realm named
- `keycloaktcdemo`.
-2. Under the `keycloaktcdemo` realm, create a client with the following
- settings:
- - **Client ID**: `product-service`
- - **Client Authentication**: `On`
- - **Authentication flow**: select only **Service accounts roles**
-3. On the **Client details** screen, go to the **Credentials** tab and copy the
- **Client secret** value.
-
-Export the realm configuration:
-
-```console
-$ docker ps
-# copy the keycloak container id
-
-$ docker exec -it /bin/bash
-
-$ /opt/keycloak/bin/kc.sh export --dir /opt/keycloak/data/import --realm keycloaktcdemo
-
-$ exit
-
-$ docker cp :/opt/keycloak/data/import/keycloaktcdemo-realm.json keycloaktcdemo-realm.json
-```
-
-Copy the exported `keycloaktcdemo-realm.json` file into `src/test/resources`.
-
-## Write tests with Testcontainers
-
-To test the secured API endpoints, you need a running Keycloak instance and a
-PostgreSQL database, plus a started Spring context. Testcontainers spins up both
-services in Docker containers and connects them to Spring through dynamic
-property registration.
-
-### Configure the test containers
-
-Spring Boot's Testcontainers support lets you declare containers as beans. For
-Keycloak, `@ServiceConnection` isn't available, but you can use
-`DynamicPropertyRegistry` to set the JWT issuer URI dynamically.
-
-Create `ContainersConfig.java` under `src/test/java`:
-
-```java
-package com.testcontainers.products;
-
-import dasniko.testcontainers.keycloak.KeycloakContainer;
-import org.springframework.boot.test.context.TestConfiguration;
-import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
-import org.springframework.context.annotation.Bean;
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.testcontainers.postgresql.PostgreSQLContainer;
-
-@TestConfiguration(proxyBeanMethods = false)
-public class ContainersConfig {
-
- static String POSTGRES_IMAGE = "postgres:16-alpine";
- static String KEYCLOAK_IMAGE = "quay.io/keycloak/keycloak:25.0";
- static String realmImportFile = "/keycloaktcdemo-realm.json";
- static String realmName = "keycloaktcdemo";
-
- @Bean
- @ServiceConnection
- PostgreSQLContainer postgres() {
- return new PostgreSQLContainer(POSTGRES_IMAGE);
- }
-
- @Bean
- KeycloakContainer keycloak(DynamicPropertyRegistry registry) {
- var keycloak = new KeycloakContainer(KEYCLOAK_IMAGE)
- .withRealmImportFile(realmImportFile);
- registry.add(
- "spring.security.oauth2.resourceserver.jwt.issuer-uri",
- () -> keycloak.getAuthServerUrl() + "/realms/" + realmName
- );
- return keycloak;
- }
-}
-```
-
-This configuration:
-
-- Declares a `PostgreSQLContainer` bean with `@ServiceConnection`, which starts
- a PostgreSQL container and automatically registers the datasource properties.
-- Declares a `KeycloakContainer` bean using the `quay.io/keycloak/keycloak:25.0`
- image, imports the realm configuration file, and dynamically registers the JWT
- issuer URI from the Keycloak container's auth server URL.
-
-### Write the test
-
-Create `ProductControllerTests.java`:
-
-```java
-package com.testcontainers.products.api;
-
-import static io.restassured.RestAssured.given;
-import static io.restassured.RestAssured.when;
-import static java.util.Collections.singletonList;
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.testcontainers.products.ContainersConfig;
-import io.restassured.RestAssured;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.web.server.LocalServerPort;
-import org.springframework.context.annotation.Import;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.util.LinkedMultiValueMap;
-import org.springframework.util.MultiValueMap;
-import org.springframework.web.client.RestTemplate;
-
-@SpringBootTest(webEnvironment = RANDOM_PORT)
-@Import(ContainersConfig.class)
-class ProductControllerTests {
-
- static final String GRANT_TYPE_CLIENT_CREDENTIALS = "client_credentials";
- static final String CLIENT_ID = "product-service";
- static final String CLIENT_SECRET = "jTJJqdzeCSt3DmypfHZa42vX8U9rQKZ9";
-
- @LocalServerPort
- private int port;
-
- @Autowired
- OAuth2ResourceServerProperties oAuth2ResourceServerProperties;
-
- @BeforeEach
- void setup() {
- RestAssured.port = port;
- }
-
- @Test
- void shouldGetProductsWithoutAuthToken() {
- when().get("/api/products").then().statusCode(200);
- }
-
- @Test
- void shouldGetUnauthorizedWhenCreateProductWithoutAuthToken() {
- given()
- .contentType("application/json")
- .body(
- """
- {
- "title": "New Product",
- "description": "Brand New Product"
- }
- """
- )
- .when()
- .post("/api/products")
- .then()
- .statusCode(401);
- }
-
- @Test
- void shouldCreateProductWithAuthToken() {
- String token = getToken();
-
- given()
- .header("Authorization", "Bearer " + token)
- .contentType("application/json")
- .body(
- """
- {
- "title": "New Product",
- "description": "Brand New Product"
- }
- """
- )
- .when()
- .post("/api/products")
- .then()
- .statusCode(201);
- }
-
- private String getToken() {
- RestTemplate restTemplate = new RestTemplate();
- HttpHeaders httpHeaders = new HttpHeaders();
- httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
-
- MultiValueMap map = new LinkedMultiValueMap<>();
- map.put("grant_type", singletonList(GRANT_TYPE_CLIENT_CREDENTIALS));
- map.put("client_id", singletonList(CLIENT_ID));
- map.put("client_secret", singletonList(CLIENT_SECRET));
-
- String authServerUrl =
- oAuth2ResourceServerProperties.getJwt().getIssuerUri() +
- "/protocol/openid-connect/token";
-
- var request = new HttpEntity<>(map, httpHeaders);
- KeyCloakToken token = restTemplate.postForObject(
- authServerUrl,
- request,
- KeyCloakToken.class
- );
-
- assert token != null;
- return token.accessToken();
- }
-
- record KeyCloakToken(@JsonProperty("access_token") String accessToken) {}
-}
-```
-
-Here's what the tests cover:
-
-- `shouldGetProductsWithoutAuthToken()` invokes `GET /api/products` without an
- `Authorization` header. Because this endpoint is configured to permit
- unauthenticated access, the response status code is 200.
-- `shouldGetUnauthorizedWhenCreateProductWithoutAuthToken()` invokes the secured
- `POST /api/products` endpoint without an `Authorization` header and asserts
- the response status code is 401 (Unauthorized).
-- `shouldCreateProductWithAuthToken()` first obtains an `access_token` using the
- Client Credentials flow. It then includes the token as a Bearer token in the
- `Authorization` header when invoking `POST /api/products` and asserts the
- response status code is 201 (Created).
-
-The `getToken()` helper method requests an access token from the Keycloak token
-endpoint using the client ID and client secret that were configured in the
-exported realm.
-
-### Use Testcontainers for local development
-
-Spring Boot's Testcontainers support also works for local development. Create
-`TestApplication.java` under `src/test/java`:
-
-```java
-package com.testcontainers.products;
-
-import org.springframework.boot.SpringApplication;
-
-public class TestApplication {
-
- public static void main(String[] args) {
- SpringApplication
- .from(Application::main)
- .with(ContainersConfig.class)
- .run(args);
- }
-}
-```
-
-Run `TestApplication.java` from your IDE instead of the main `Application.java`.
-It starts the containers defined in `ContainersConfig` and configures the
-application to use the dynamically registered properties, so you don't have to
-install or configure PostgreSQL and Keycloak manually.
-
-## Run tests and next steps
-
-### Run the tests
-
-```console
-$ ./mvnw test
-```
-
-Or with Gradle:
-
-```console
-$ ./gradlew test
-```
-
-You should see the Keycloak and PostgreSQL Docker containers start with the
-realm settings imported and the tests pass. After the tests finish, the
-containers stop and are removed automatically.
-
-### Summary
-
-The Testcontainers Keycloak module lets you develop and test applications using a
-real Keycloak server instead of mocks. Testing against a real OAuth 2.0
-provider that mirrors your production setup gives you more confidence in your
-security configuration and token-based authentication flows.
-
-To learn more about Testcontainers, visit the
-[Testcontainers overview](https://testcontainers.com/getting-started/).
-
-### Further reading
-
-- [Getting started with Testcontainers in a Java Spring Boot project](https://testcontainers.com/guides/testing-spring-boot-rest-api-using-testcontainers/)
-- [Testcontainers Keycloak module](https://testcontainers.com/modules/keycloak/)
-- [testcontainers-keycloak GitHub repository](https://github.com/dasniko/testcontainers-keycloak)
-- [Spring Boot OAuth 2.0 Resource Server](https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/index.html)
diff --git a/content/guides/testcontainers-java-micronaut-kafka.md b/content/guides/testcontainers-java-micronaut-kafka.md
deleted file mode 100644
index 4aa9e92637..0000000000
--- a/content/guides/testcontainers-java-micronaut-kafka.md
+++ /dev/null
@@ -1,472 +0,0 @@
----
-title: Testing Micronaut Kafka Listener using Testcontainers
-linkTitle: Micronaut Kafka
-description: Learn how to test a Micronaut Kafka listener using Testcontainers with Kafka and MySQL modules.
-keywords: testcontainers, java, micronaut, testing, kafka, mysql, jpa, awaitility
-summary: |
- Learn how to create a Micronaut application with a Kafka listener that persists data in MySQL,
- then test it using Testcontainers Kafka and MySQL modules with Awaitility.
-aliases:
- - /guides/testcontainers-java-micronaut-kafka/create-project/
- - /guides/testcontainers-java-micronaut-kafka/run-tests/
- - /guides/testcontainers-java-micronaut-kafka/write-tests/
-params:
- tags: [testing]
- time: 25 minutes
----
-
-
-
-
-In this guide, you'll learn how to:
-
-- Create a Micronaut application with Kafka integration
-- Implement a Kafka listener and persist data in a MySQL database
-- Test the Kafka listener using Testcontainers and Awaitility
-
-## Prerequisites
-
-- Java 17+
-- Maven or Gradle
-- A Docker environment supported by Testcontainers
-
-> [!NOTE]
-> If you're new to Testcontainers, visit the
-> [Testcontainers overview](https://testcontainers.com/getting-started/) to learn more about
-> Testcontainers and the benefits of using it.
-
-## Create the Micronaut project
-
-### Set up the project
-
-Create a Micronaut project from [Micronaut Launch](https://micronaut.io/launch) by
-selecting the **kafka**, **data-jpa**, **mysql**, **awaitility**, **assertj**, and
-**testcontainers** features.
-
-Alternatively, clone the
-[guide repository](https://github.com/testcontainers/tc-guide-testing-micronaut-kafka-listener).
-
-You'll use the [Awaitility](http://www.awaitility.org/) library to assert the
-expectations of an asynchronous process flow.
-
-The key dependencies in `pom.xml` are:
-
-```xml
-
- io.micronaut.platform
- micronaut-parent
- 4.1.4
-
-
-
- io.micronaut.data
- micronaut-data-hibernate-jpa
- compile
-
-
- io.micronaut.kafka
- micronaut-kafka
- compile
-
-
- io.micronaut.serde
- micronaut-serde-jackson
- compile
-
-
- io.micronaut.sql
- micronaut-jdbc-hikari
- compile
-
-
- mysql
- mysql-connector-java
- runtime
-
-
- org.awaitility
- awaitility
- 4.2.0
- test
-
-
- org.testcontainers
- testcontainers-junit-jupiter
- test
-
-
- org.testcontainers
- testcontainers-kafka
- test
-
-
- org.testcontainers
- testcontainers-mysql
- test
-
-
-```
-
-The Micronaut parent POM manages the Testcontainers BOM, so you don't need to
-specify versions for Testcontainers modules individually.
-
-### Create the JPA entity
-
-The application listens to a topic called `product-price-changes`. When a
-message arrives, it extracts the product code and price from the event payload
-and updates the price for that product in the MySQL database.
-
-Create `Product.java`:
-
-```java
-package com.testcontainers.demo;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.Table;
-import java.math.BigDecimal;
-
-@Entity
-@Table(name = "products")
-public class Product {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
-
- @Column(nullable = false, unique = true)
- private String code;
-
- @Column(nullable = false)
- private String name;
-
- @Column(nullable = false)
- private BigDecimal price;
-
- public Product() {}
-
- public Product(Long id, String code, String name, BigDecimal price) {
- this.id = id;
- this.code = code;
- this.name = name;
- this.price = price;
- }
-
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getCode() {
- return code;
- }
-
- public void setCode(String code) {
- this.code = code;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public BigDecimal getPrice() {
- return price;
- }
-
- public void setPrice(BigDecimal price) {
- this.price = price;
- }
-}
-```
-
-### Create the Micronaut Data JPA repository
-
-Create a repository interface for the `Product` entity with a method to find a
-product by code and a method to update the price for a given product code:
-
-```java
-package com.testcontainers.demo;
-
-import io.micronaut.data.annotation.Query;
-import io.micronaut.data.annotation.Repository;
-import io.micronaut.data.jpa.repository.JpaRepository;
-import java.math.BigDecimal;
-import java.util.Optional;
-
-@Repository
-public interface ProductRepository extends JpaRepository {
-
- Optional findByCode(String code);
-
- @Query("update Product p set p.price = :price where p.code = :productCode")
- void updateProductPrice(String productCode, BigDecimal price);
-}
-```
-
-Unlike Spring Data JPA, Micronaut Data uses compile-time annotation processing
-to implement repository methods, avoiding runtime reflection.
-
-### Create the event payload
-
-Create a record named `ProductPriceChangedEvent` that represents the structure
-of the event payload received from the Kafka topic:
-
-```java
-package com.testcontainers.demo;
-
-import io.micronaut.serde.annotation.Serdeable;
-import java.math.BigDecimal;
-
-@Serdeable
-public record ProductPriceChangedEvent(String productCode, BigDecimal price) {}
-```
-
-The `@Serdeable` annotation tells Micronaut Serialization that this type can be
-serialized and deserialized.
-
-The sender and receiver agree on the following JSON format:
-
-```json
-{
- "productCode": "P100",
- "price": 25.0
-}
-```
-
-### Implement the Kafka listener
-
-Create `ProductPriceChangedEventHandler.java`, which handles messages from the
-`product-price-changes` topic and updates the product price in the database:
-
-```java
-package com.testcontainers.demo;
-
-import static io.micronaut.configuration.kafka.annotation.OffsetReset.EARLIEST;
-
-import io.micronaut.configuration.kafka.annotation.KafkaListener;
-import io.micronaut.configuration.kafka.annotation.Topic;
-import jakarta.inject.Singleton;
-import jakarta.transaction.Transactional;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Singleton
-@Transactional
-class ProductPriceChangedEventHandler {
-
- private static final Logger LOG = LoggerFactory.getLogger(ProductPriceChangedEventHandler.class);
-
- private final ProductRepository productRepository;
-
- ProductPriceChangedEventHandler(ProductRepository productRepository) {
- this.productRepository = productRepository;
- }
-
- @Topic("product-price-changes")
- @KafkaListener(offsetReset = EARLIEST, groupId = "demo")
- public void handle(ProductPriceChangedEvent event) {
- LOG.info("Received a ProductPriceChangedEvent with productCode:{}: ", event.productCode());
- productRepository.updateProductPrice(event.productCode(), event.price());
- }
-}
-```
-
-Key details:
-
-- The `@KafkaListener` annotation marks this class as a Kafka message listener.
- Setting `offsetReset` to `EARLIEST` makes the listener start consuming
- messages from the beginning of the partition, which is useful during testing.
-- The `@Topic` annotation specifies which topic to subscribe to.
-- Micronaut handles JSON deserialization of the `ProductPriceChangedEvent`
- automatically using Micronaut Serialization.
-
-### Configure the datasource
-
-Add the following properties to `src/main/resources/application.properties`:
-
-```properties
-micronaut.application.name=tc-guide-testing-micronaut-kafka-listener
-datasources.default.db-type=mysql
-datasources.default.dialect=MYSQL
-jpa.default.properties.hibernate.hbm2ddl.auto=update
-jpa.default.entity-scan.packages=com.testcontainers.demo
-datasources.default.driver-class-name=com.mysql.cj.jdbc.Driver
-```
-
-Hibernate's `hbm2ddl.auto=update` creates and updates the database schema
-automatically. For testing, you'll override this to `create-drop` in the test
-properties file.
-
-Create `src/test/resources/application-test.properties`:
-
-```properties
-jpa.default.properties.hibernate.hbm2ddl.auto=create-drop
-```
-
-## Write tests with Testcontainers
-
-To test the Kafka listener, you need a running Kafka broker and a MySQL
-database, plus a started Micronaut application context. Testcontainers spins up
-both services in Docker containers and the `TestPropertyProvider` interface
-connects them to Micronaut.
-
-### Create a Kafka client for testing
-
-First, create a `@KafkaClient` interface to publish events in the test:
-
-```java
-package com.testcontainers.demo;
-
-import io.micronaut.configuration.kafka.annotation.KafkaClient;
-import io.micronaut.configuration.kafka.annotation.KafkaKey;
-import io.micronaut.configuration.kafka.annotation.Topic;
-
-@KafkaClient
-public interface ProductPriceChangesClient {
-
- @Topic("product-price-changes")
- void send(@KafkaKey String productCode, ProductPriceChangedEvent event);
-}
-```
-
-Key details:
-
-- The `@KafkaClient` annotation designates this interface as a Kafka producer.
-- The `@Topic` annotation specifies the target topic.
-- The `@KafkaKey` annotation marks the parameter used as the Kafka message key.
- If no such parameter exists, Micronaut sends the record with a null key.
-
-### Write the test
-
-Create `ProductPriceChangedEventHandlerTest.java`:
-
-```java
-package com.testcontainers.demo;
-
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.awaitility.Awaitility.await;
-
-import io.micronaut.context.annotation.Property;
-import io.micronaut.core.annotation.NonNull;
-import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
-import io.micronaut.test.support.TestPropertyProvider;
-import java.math.BigDecimal;
-import java.time.Duration;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Optional;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import org.testcontainers.kafka.ConfluentKafkaContainer;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
-
-@MicronautTest(transactional = false)
-@Property(name = "datasources.default.driver-class-name", value = "org.testcontainers.jdbc.ContainerDatabaseDriver")
-@Property(name = "datasources.default.url", value = "jdbc:tc:mysql:8.0.32:///db")
-@Testcontainers(disabledWithoutDocker = true)
-@TestInstance(TestInstance.Lifecycle.PER_CLASS)
-class ProductPriceChangedEventHandlerTest implements TestPropertyProvider {
-
- @Container
- static final ConfluentKafkaContainer kafka = new ConfluentKafkaContainer("confluentinc/cp-kafka:7.8.0");
-
- @Override
- public @NonNull Map getProperties() {
- if (!kafka.isRunning()) {
- kafka.start();
- }
- return Collections.singletonMap("kafka.bootstrap.servers", kafka.getBootstrapServers());
- }
-
- @Test
- void shouldHandleProductPriceChangedEvent(
- ProductPriceChangesClient productPriceChangesClient, ProductRepository productRepository) {
- Product product = new Product(null, "P100", "Product One", BigDecimal.TEN);
- Long id = productRepository.save(product).getId();
-
- ProductPriceChangedEvent event = new ProductPriceChangedEvent("P100", new BigDecimal("14.50"));
-
- productPriceChangesClient.send(event.productCode(), event);
-
- await().pollInterval(Duration.ofSeconds(3)).atMost(10, SECONDS).untilAsserted(() -> {
- Optional optionalProduct = productRepository.findByCode("P100");
- assertThat(optionalProduct).isPresent();
- assertThat(optionalProduct.get().getCode()).isEqualTo("P100");
- assertThat(optionalProduct.get().getPrice()).isEqualTo(new BigDecimal("14.50"));
- });
-
- productRepository.deleteById(id);
- }
-}
-```
-
-Here's what the test does:
-
-- `@MicronautTest` initializes the Micronaut application context and the
- embedded server. Setting `transactional` to `false` prevents each test method
- from running inside a rolled-back transaction, which is necessary because the
- Kafka listener processes messages in a separate thread.
-- The `@Property` annotations override the datasource driver and URL to use the
- Testcontainers special JDBC URL (`jdbc:tc:mysql:8.0.32:///db`). This spins up
- a MySQL container and configures it as the datasource automatically.
-- `@Testcontainers` and `@Container` manage the Kafka container lifecycle.
- The `TestPropertyProvider` interface registers the Kafka bootstrap servers
- with Micronaut so that the producer and consumer connect to the test container.
-- `@TestInstance(TestInstance.Lifecycle.PER_CLASS)` creates a single test
- instance for all test methods, which is required when implementing
- `TestPropertyProvider`.
-- The test creates a `Product` record in the database, then sends a
- `ProductPriceChangedEvent` to the `product-price-changes` topic using the
- `ProductPriceChangesClient`.
-- Because Kafka message processing is asynchronous, the test uses
- [Awaitility](http://www.awaitility.org/) to poll every 3 seconds (up to a
- maximum of 10 seconds) until the product price in the database matches the
- expected value.
-
-## Run tests and next steps
-
-### Run the tests
-
-```console
-$ ./mvnw test
-```
-
-Or with Gradle:
-
-```console
-$ ./gradlew test
-```
-
-You should see the Kafka and MySQL Docker containers start and all tests pass.
-After the tests finish, the containers stop and are removed automatically.
-
-### Summary
-
-Testing with real Kafka and MySQL instances gives you more confidence in the
-correctness of your code than mocks or embedded alternatives. The
-Testcontainers library manages the container lifecycle so that your integration
-tests run against the same services you use in production.
-
-To learn more about Testcontainers, visit the
-[Testcontainers overview](https://testcontainers.com/getting-started/).
-
-### Further reading
-
-- [Testing REST API integrations in Micronaut apps using WireMock](/guides/testcontainers-java-micronaut-wiremock/)
-- [Testing Spring Boot Kafka Listener using Testcontainers](/guides/testcontainers-java-spring-boot-kafka/)
-- [Getting started with Testcontainers in a Java Spring Boot project](https://testcontainers.com/guides/testing-spring-boot-rest-api-using-testcontainers/)
-- [Awaitility](http://www.awaitility.org/)
-- [Testcontainers Kafka module](https://java.testcontainers.org/modules/kafka/)
-- [Testcontainers MySQL module](https://java.testcontainers.org/modules/databases/mysql/)
diff --git a/content/guides/testcontainers-java-micronaut-wiremock.md b/content/guides/testcontainers-java-micronaut-wiremock.md
deleted file mode 100644
index 9cd2b0b052..0000000000
--- a/content/guides/testcontainers-java-micronaut-wiremock.md
+++ /dev/null
@@ -1,673 +0,0 @@
----
-title: Testing REST API integrations in Micronaut apps using WireMock
-linkTitle: Micronaut WireMock
-description: Learn how to test REST API integrations in a Micronaut application using the Testcontainers WireMock module.
-keywords: testcontainers, java, micronaut, testing, wiremock, rest api
-summary: |
- Learn how to create a Micronaut application that integrates with
- external REST APIs, then test those integrations using WireMock
- and the Testcontainers WireMock module.
-aliases:
- - /guides/testcontainers-java-micronaut-wiremock/create-project/
- - /guides/testcontainers-java-micronaut-wiremock/run-tests/
- - /guides/testcontainers-java-micronaut-wiremock/write-tests/
-params:
- tags: [testing]
- time: 20 minutes
----
-
-
-
-
-In this guide, you'll learn how to:
-
-- Create a Micronaut application that talks to external REST APIs
-- Test external API integrations using WireMock
-- Use the Testcontainers WireMock module to run WireMock as a Docker container
-
-## Prerequisites
-
-- Java 17+
-- Maven or Gradle
-- A Docker environment supported by Testcontainers
-
-> [!NOTE]
-> If you're new to Testcontainers, visit the
-> [Testcontainers overview](https://testcontainers.com/getting-started/) to learn more about
-> Testcontainers and the benefits of using it.
-
-## Create the Micronaut project
-
-### Set up the project
-
-Create a Micronaut project from [Micronaut Launch](https://micronaut.io/launch)
-by selecting the **http-client**, **micronaut-test-rest-assured**, and
-**testcontainers** features.
-
-Alternatively, clone the
-[guide repository](https://github.com/testcontainers/tc-guide-testing-rest-api-integrations-in-micronaut-apps-using-wiremock).
-
-After generating the project, add the **WireMock** and **Testcontainers
-WireMock** libraries as test dependencies. The key dependencies in `pom.xml`
-are:
-
-```xml
-
- io.micronaut.platform
- micronaut-parent
- 4.1.2
-
-
-
- 17
- 4.1.2
- netty
-
-
-
-
- jitpack.io
- https://jitpack.io
-
-
-
-
-
- io.micronaut
- micronaut-http-client
- compile
-
-
- io.micronaut
- micronaut-http-server-netty
- compile
-
-
- io.micronaut.serde
- micronaut-serde-jackson
- compile
-
-
- io.micronaut.test
- micronaut-test-junit5
- test
-
-
- io.micronaut.test
- micronaut-test-rest-assured
- test
-
-
- org.testcontainers
- testcontainers-junit-jupiter
- test
-
-
- org.testcontainers
- testcontainers
- test
-
-
- org.wiremock
- wiremock-standalone
- 3.2.0
- test
-
-
- org.wiremock.integrations.testcontainers
- wiremock-testcontainers-module
- 1.0-alpha-13
- test
-
-
-```
-
-This guide builds an application that manages video albums. A third-party REST
-API handles photo assets. For demonstration purposes, the application uses the
-publicly available [JSONPlaceholder](https://jsonplaceholder.typicode.com/) API
-as a photo service.
-
-The application exposes a `GET /api/albums/{albumId}` endpoint that calls the
-photo service to fetch photos for a given album.
-[WireMock](https://wiremock.org/) is a tool for building mock APIs.
-Testcontainers provides a
-[WireMock module](https://testcontainers.com/modules/wiremock/) that runs
-WireMock as a Docker container.
-
-### Create the Album and Photo models
-
-Create `Album.java` using Java records. Annotate both records with `@Serdeable`
-to allow serialization and deserialization:
-
-```java
-package com.testcontainers.demo;
-
-import io.micronaut.serde.annotation.Serdeable;
-import java.util.List;
-
-@Serdeable
-public record Album(Long albumId, List photos) {}
-
-@Serdeable
-record Photo(Long id, String title, String url, String thumbnailUrl) {}
-```
-
-### Create the PhotoServiceClient
-
-Micronaut provides
-[declarative HTTP client](https://docs.micronaut.io/latest/guide/#httpClient)
-support. Create an interface with a method that fetches photos for a given album
-ID:
-
-```java
-package com.testcontainers.demo;
-
-import io.micronaut.http.annotation.Get;
-import io.micronaut.http.annotation.PathVariable;
-import io.micronaut.http.client.annotation.Client;
-import java.util.List;
-
-@Client(id = "photosapi")
-interface PhotoServiceClient {
-
- @Get("/albums/{albumId}/photos")
- List getPhotos(@PathVariable Long albumId);
-}
-```
-
-The `@Client(id = "photosapi")` annotation ties this client to a named
-configuration. Add the following property to
-`src/main/resources/application.properties` to set the base URL:
-
-```properties
-micronaut.http.services.photosapi.url=https://jsonplaceholder.typicode.com
-```
-
-### Create the REST API endpoint
-
-Create `AlbumController.java`:
-
-```java
-package com.testcontainers.demo;
-
-import static io.micronaut.scheduling.TaskExecutors.BLOCKING;
-
-import io.micronaut.http.annotation.Controller;
-import io.micronaut.http.annotation.Get;
-import io.micronaut.http.annotation.PathVariable;
-import io.micronaut.scheduling.annotation.ExecuteOn;
-
-@Controller("/api")
-class AlbumController {
-
- private final PhotoServiceClient photoServiceClient;
-
- AlbumController(PhotoServiceClient photoServiceClient) {
- this.photoServiceClient = photoServiceClient;
- }
-
- @ExecuteOn(BLOCKING)
- @Get("/albums/{albumId}")
- public Album getAlbumById(@PathVariable Long albumId) {
- return new Album(albumId, photoServiceClient.getPhotos(albumId));
- }
-}
-```
-
-Here's what this controller does:
-
-- `@Controller("/api")` maps the controller to the `/api` path.
-- Constructor injection provides a `PhotoServiceClient` bean.
-- `@ExecuteOn(BLOCKING)` offloads blocking I/O to a separate thread pool so it
- doesn't block the event loop.
-- `@Get("/albums/{albumId}")` maps the `getAlbumById()` method to an HTTP GET
- request.
-
-This endpoint calls the photo service for a given album ID and returns a
-response like:
-
-```json
-{
- "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"
- }
- ]
-}
-```
-
-## Write tests with WireMock and Testcontainers
-
-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.
-
-### Test with WireMock's JUnit 5 extension
-
-The first approach uses WireMock's `WireMockExtension` to start an in-process
-WireMock server on a dynamic port.
-
-Create `AlbumControllerTest.java`:
-
-```java
-package com.testcontainers.demo;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
-import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
-import static io.restassured.RestAssured.given;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.Matchers.hasSize;
-
-import com.github.tomakehurst.wiremock.client.WireMock;
-import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
-import io.micronaut.context.ApplicationContext;
-import io.micronaut.http.MediaType;
-import io.micronaut.runtime.server.EmbeddedServer;
-import io.restassured.RestAssured;
-import io.restassured.http.ContentType;
-import java.util.Collections;
-import java.util.Map;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
-
-class AlbumControllerTest {
-
- @RegisterExtension
- static WireMockExtension wireMock = WireMockExtension.newInstance()
- .options(wireMockConfig().dynamicPort())
- .build();
-
- private Map getProperties() {
- return Collections.singletonMap("micronaut.http.services.photosapi.url", wireMock.baseUrl());
- }
-
- @Test
- void shouldGetAlbumById() {
- try (EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class, getProperties())) {
- RestAssured.port = server.getPort();
- Long albumId = 1L;
- String responseJson =
- """
- [
- {
- "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"
- }
- ]
- """;
- wireMock.stubFor(WireMock.get(urlMatching("/albums/" + albumId + "/photos"))
- .willReturn(aResponse()
- .withHeader("Content-Type", MediaType.APPLICATION_JSON)
- .withBody(responseJson)));
-
- given().contentType(ContentType.JSON)
- .when()
- .get("/api/albums/{albumId}", albumId)
- .then()
- .statusCode(200)
- .body("albumId", is(albumId.intValue()))
- .body("photos", hasSize(2));
- }
- }
-
- @Test
- void shouldReturnServerErrorWhenPhotoServiceCallFailed() {
- try (EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class, getProperties())) {
- RestAssured.port = server.getPort();
- Long albumId = 2L;
- wireMock.stubFor(WireMock.get(urlMatching("/albums/" + albumId + "/photos"))
- .willReturn(aResponse().withStatus(500)));
-
- given().contentType(ContentType.JSON)
- .when()
- .get("/api/albums/{albumId}", albumId)
- .then()
- .statusCode(500);
- }
- }
-}
-```
-
-Here's what this test does:
-
-- `WireMockExtension` starts a WireMock server on a dynamic port.
-- The `getProperties()` method overrides `micronaut.http.services.photosapi.url`
- to point at the WireMock endpoint, so the application talks to WireMock
- instead of the real photo service.
-- `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.
-- `shouldReturnServerErrorWhenPhotoServiceCallFailed()` configures WireMock to
- return a 500 status and verifies the application propagates that error.
-
-### Stub using JSON mapping files
-
-Instead of stubbing with the WireMock Java API, you can use JSON mapping-based
-configuration.
-
-Create `src/test/resources/wiremock/mappings/get-album-photos.json`:
-
-```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": []
- }
- }
- ]
-}
-```
-
-Create `src/test/resources/wiremock/__files/album-photos-resp-200.json`:
-
-```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"
- }
-]
-```
-
-Then initialize WireMock to load stub mappings from these files:
-
-```java
-@RegisterExtension
-static WireMockExtension wireMock = WireMockExtension.newInstance()
- .options(
- wireMockConfig()
- .dynamicPort()
- .usingFilesUnderClasspath("wiremock")
- )
- .build();
-```
-
-With mapping files-based stubbing in place, write tests without needing
-programmatic stubs:
-
-```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));
- }
-}
-```
-
-### Use the Testcontainers WireMock module
-
-The [Testcontainers WireMock module](https://testcontainers.com/modules/wiremock/)
-provisions a WireMock server as a standalone container within your tests, based
-on [WireMock Docker](https://github.com/wiremock/wiremock-docker).
-
-Create `src/test/resources/mocks-config.json` with the stub mappings:
-
-```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": []
- }
- }
- ]
-}
-```
-
-Create `src/test/resources/album-photos-response.json`:
-
-```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"
- }
-]
-```
-
-Create `AlbumControllerTestcontainersTests.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.hamcrest.Matchers.nullValue;
-
-import io.micronaut.context.ApplicationContext;
-import io.micronaut.core.annotation.NonNull;
-import io.micronaut.runtime.server.EmbeddedServer;
-import io.restassured.RestAssured;
-import io.restassured.http.ContentType;
-import java.util.Collections;
-import java.util.Map;
-import org.junit.jupiter.api.Test;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
-import org.wiremock.integrations.testcontainers.WireMockContainer;
-
-@Testcontainers(disabledWithoutDocker = true)
-class AlbumControllerTestcontainersTests {
-
- @Container
- static WireMockContainer wiremockServer = new WireMockContainer("wiremock/wiremock:2.35.0")
- .withMappingFromResource("mocks-config.json")
- .withFileFromResource("album-photos-response.json");
-
- @NonNull public Map getProperties() {
- return Collections.singletonMap("micronaut.http.services.photosapi.url", wiremockServer.getBaseUrl());
- }
-
- @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));
- }
- }
-
- @Test
- void shouldReturnServerErrorWhenPhotoServiceCallFailed() {
- Long albumId = 2L;
- try (EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class, getProperties())) {
- RestAssured.port = server.getPort();
- given().contentType(ContentType.JSON)
- .when()
- .get("/api/albums/{albumId}", albumId)
- .then()
- .statusCode(500);
- }
- }
-
- @Test
- void shouldReturnEmptyPhotos() {
- Long albumId = 3L;
- 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", nullValue());
- }
- }
-}
-```
-
-Here's what this test does:
-
-- `@Testcontainers` and `@Container` annotations start a `WireMockContainer`
- using the `wiremock/wiremock:2.35.0` Docker image.
-- `withMappingFromResource("mocks-config.json")` loads stub mappings from the
- classpath resource.
-- `withFileFromResource("album-photos-response.json")` makes the response body
- file available to WireMock.
-- `getProperties()` overrides the photo service URL to point at the WireMock
- container's base URL.
-- `shouldGetAlbumById()` verifies that the application returns the expected
- album with two photos.
-- `shouldReturnServerErrorWhenPhotoServiceCallFailed()` verifies that a 500
- from the photo service propagates to the caller.
-- `shouldReturnEmptyPhotos()` verifies the application handles an empty photo
- list.
-
-## Run tests and next steps
-
-### Run the tests
-
-```console
-$ ./mvnw test
-```
-
-Or with Gradle:
-
-```console
-$ ./gradlew test
-```
-
-You should see the WireMock Docker container start in the console output. It
-acts as the photo service, serving mock responses based on the configured
-expectations. All tests should pass.
-
-### Summary
-
-You built a Micronaut application that integrates with an external REST API
-using declarative HTTP clients, then tested that integration using WireMock and
-the Testcontainers WireMock module. Testing at the HTTP protocol level instead
-of mocking Java methods lets you catch serialization issues and simulate
-realistic failure scenarios.
-
-> [!TIP]
-> Testcontainers WireMock modules are available for Go and Python as well.
-
-To learn more about Testcontainers, visit the
-[Testcontainers overview](https://testcontainers.com/getting-started/).
-
-### Further reading
-
-- [Testcontainers WireMock module](https://testcontainers.com/modules/wiremock/)
-- [WireMock documentation](https://wiremock.org/docs/)
-- [Testcontainers JUnit 5 quickstart](https://java.testcontainers.org/quickstart/junit_5_quickstart/)
-- [Testing REST API integrations in Spring Boot using WireMock](/guides/testcontainers-java-wiremock/)
diff --git a/content/guides/testcontainers-java-mockserver.md b/content/guides/testcontainers-java-mockserver.md
deleted file mode 100644
index fe8a48c137..0000000000
--- a/content/guides/testcontainers-java-mockserver.md
+++ /dev/null
@@ -1,445 +0,0 @@
----
-title: Testing REST API integrations using MockServer
-linkTitle: MockServer
-description: Learn how to test REST API integrations in a Spring Boot application using the Testcontainers MockServer module.
-keywords: testcontainers, java, spring boot, testing, mockserver, rest api, rest assured
-summary: |
- Learn how to create a Spring Boot application that integrates with
- external REST APIs, then test those integrations using Testcontainers
- and MockServer.
-aliases:
- - /guides/testcontainers-java-mockserver/create-project/
- - /guides/testcontainers-java-mockserver/run-tests/
- - /guides/testcontainers-java-mockserver/write-tests/
-params:
- tags: [testing]
- time: 20 minutes
----
-
-
-
-
-In this guide, you will learn how to:
-
-- Create a Spring Boot application that talks to external REST APIs
-- Test external API integrations using the Testcontainers MockServer module
-
-## Prerequisites
-
-- Java 17+
-- Maven or Gradle
-- A Docker environment supported by Testcontainers
-
-> [!NOTE]
-> If you're new to Testcontainers, visit the
-> [Testcontainers overview](https://testcontainers.com/getting-started/) to learn more about
-> Testcontainers and the benefits of using it.
-
-## Create the Spring Boot project
-
-### Set up the project
-
-Create a Spring Boot project from [Spring Initializr](https://start.spring.io)
-by selecting the **Spring Web**, **Spring Reactive Web**, and **Testcontainers**
-starters.
-
-Alternatively, clone the
-[guide repository](https://github.com/testcontainers/tc-guide-testing-rest-api-integrations-using-mockserver).
-
-After generating the project, add the **REST Assured** and **MockServer**
-libraries as test dependencies. The key dependencies in `pom.xml` are:
-
-```xml
-
- 17
- 2.0.4
-
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-starter-webflux
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.testcontainers
- testcontainers-junit-jupiter
- test
-
-
- org.testcontainers
- testcontainers-mockserver
- test
-
-
- org.mock-server
- mockserver-netty
- 5.15.0
- test
-
-
- io.rest-assured
- rest-assured
- test
-
-
-```
-
-Using the Testcontainers BOM (Bill of Materials) is recommended so that you
-don't have to repeat the version for every Testcontainers module dependency.
-
-This guide builds an application that manages video albums. A third-party REST
-API handles photo assets. For demonstration purposes, the application uses the
-publicly available [JSONPlaceholder](https://jsonplaceholder.typicode.com/) API
-as a photo service.
-
-The application exposes a `GET /api/albums/{albumId}` endpoint that calls the
-photo service to fetch photos for a given album.
-[MockServer](https://www.mock-server.com/) is a library for mocking HTTP-based
-services. Testcontainers provides a
-[MockServer module](https://java.testcontainers.org/modules/mockserver/) that
-runs MockServer as a Docker container.
-
-### Create the Album and Photo models
-
-Create `Album.java` using Java records:
-
-```java
-package com.testcontainers.demo;
-
-import java.util.List;
-
-public record Album(Long albumId, List photos) {}
-
-record Photo(Long id, String title, String url, String thumbnailUrl) {}
-```
-
-### Create the PhotoServiceClient interface
-
-Spring Framework 6 introduced
-[declarative HTTP client support](https://docs.spring.io/spring-framework/reference/integration/rest-clients.html#rest-http-interface).
-Create an interface with a method that fetches photos for a given album ID:
-
-```java
-package com.testcontainers.demo;
-
-import java.util.List;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.service.annotation.GetExchange;
-
-interface PhotoServiceClient {
- @GetExchange("/albums/{albumId}/photos")
- List getPhotos(@PathVariable Long albumId);
-}
-```
-
-### Register PhotoServiceClient as a bean
-
-To generate a runtime implementation of `PhotoServiceClient`, register it as a
-Spring bean using `HttpServiceProxyFactory`. The factory requires an
-`HttpClientAdapter` implementation. Spring Boot provides `WebClientAdapter` as
-part of the `spring-webflux` library:
-
-```java
-package com.testcontainers.demo;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.reactive.function.client.WebClient;
-import org.springframework.web.reactive.function.client.support.WebClientAdapter;
-import org.springframework.web.service.invoker.HttpServiceProxyFactory;
-
-@Configuration
-public class AppConfig {
-
- @Bean
- public PhotoServiceClient photoServiceClient(
- @Value("${photos.api.base-url}") String photosApiBaseUrl
- ) {
- WebClient client = WebClient.builder().baseUrl(photosApiBaseUrl).build();
- HttpServiceProxyFactory factory = HttpServiceProxyFactory
- .builder(WebClientAdapter.forClient(client))
- .build();
- return factory.createClient(PhotoServiceClient.class);
- }
-}
-```
-
-The photo service base URL is externalized as a configuration property. Add the
-following entry to `src/main/resources/application.properties`:
-
-```properties
-photos.api.base-url=https://jsonplaceholder.typicode.com
-```
-
-### Create the REST API endpoint
-
-Create `AlbumController.java`:
-
-```java
-package com.testcontainers.demo;
-
-import java.util.List;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.reactive.function.client.WebClientResponseException;
-
-@RestController
-@RequestMapping("/api")
-class AlbumController {
-
- private static final Logger logger = LoggerFactory.getLogger(
- AlbumController.class
- );
-
- private final PhotoServiceClient photoServiceClient;
-
- AlbumController(PhotoServiceClient photoServiceClient) {
- this.photoServiceClient = photoServiceClient;
- }
-
- @GetMapping("/albums/{albumId}")
- public ResponseEntity getAlbumById(@PathVariable Long albumId) {
- try {
- List photos = photoServiceClient.getPhotos(albumId);
- return ResponseEntity.ok(new Album(albumId, photos));
- } catch (WebClientResponseException e) {
- logger.error("Failed to get photos", e);
- return new ResponseEntity<>(e.getStatusCode());
- }
- }
-}
-```
-
-This endpoint calls the photo service for a given album ID and returns a
-response like:
-
-```json
-{
- "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"
- }
- ]
-}
-```
-
-## Write tests with Testcontainers MockServer
-
-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.
-
-## Run tests and next steps
-
-### Run the tests
-
-```console
-$ ./mvnw test
-```
-
-Or with Gradle:
-
-```console
-$ ./gradlew test
-```
-
-You should see the MockServer Docker container start in the console output. It
-acts as the photo service, serving mock responses based on the configured
-expectations. All tests should pass.
-
-### Summary
-
-You built a Spring Boot application that integrates with an external REST API
-using declarative HTTP clients, then tested that integration using the
-Testcontainers MockServer module. Testing at the HTTP protocol level instead of
-mocking Java methods lets you catch serialization issues and simulate realistic
-failure scenarios.
-
-To learn more about Testcontainers, visit the
-[Testcontainers overview](https://testcontainers.com/getting-started/).
-
-### Further reading
-
-- [Testcontainers MockServer module](https://java.testcontainers.org/modules/mockserver/)
-- [MockServer documentation](https://www.mock-server.com/)
-- [Testcontainers JUnit 5 quickstart](https://java.testcontainers.org/quickstart/junit_5_quickstart/)
diff --git a/content/guides/testcontainers-java-quarkus.md b/content/guides/testcontainers-java-quarkus.md
deleted file mode 100644
index 71b1447de4..0000000000
--- a/content/guides/testcontainers-java-quarkus.md
+++ /dev/null
@@ -1,474 +0,0 @@
----
-title: Testing Quarkus applications with Testcontainers
-linkTitle: Quarkus
-description: Learn how to test a Quarkus REST API using Testcontainers with PostgreSQL, Hibernate ORM with Panache, and REST Assured.
-keywords: testcontainers, java, quarkus, testing, postgresql, rest api, rest assured, panache, dev services
-summary: |
- Learn how to create a Quarkus REST API with Hibernate ORM with Panache and PostgreSQL,
- then test it using Quarkus Dev Services, Testcontainers, and REST Assured.
-aliases:
- - /guides/testcontainers-java-quarkus/create-project/
- - /guides/testcontainers-java-quarkus/run-tests/
- - /guides/testcontainers-java-quarkus/write-tests/
-params:
- tags: [testing]
- time: 25 minutes
----
-
-
-
-
-In this guide, you'll learn how to:
-
-- Create a Quarkus application with REST API endpoints
-- Use Hibernate ORM with Panache and PostgreSQL for persistence
-- Test the REST API using Quarkus Dev Services, which uses Testcontainers behind
- the scenes
-- Test with services not supported by Dev Services using
- `QuarkusTestResourceLifecycleManager`
-
-## Prerequisites
-
-- Java 17+
-- Maven or Gradle
-- A Docker environment supported by Testcontainers
-
-> [!NOTE]
-> If you're new to Testcontainers, visit the
-> [Testcontainers overview](https://testcontainers.com/getting-started/) to learn more about
-> Testcontainers and the benefits of using it.
-
-## Create the Quarkus project
-
-### Set up the project
-
-Create a Quarkus project from [code.quarkus.io](https://code.quarkus.io/) by
-selecting the **RESTEasy Classic**, **RESTEasy Classic Jackson**,
-**Hibernate Validator**, **Hibernate ORM with Panache**, **JDBC Driver -
-PostgreSQL**, and **Flyway** extensions.
-
-Alternatively, clone the
-[guide repository](https://github.com/testcontainers/tc-guide-testcontainers-in-quarkus-applications).
-
-The key dependencies in `pom.xml` are:
-
-```xml
-
- 3.22.3
-
-
-
- io.quarkus
- quarkus-hibernate-orm-panache
-
-
- io.quarkus
- quarkus-flyway
-
-
- io.quarkus
- quarkus-hibernate-validator
-
-
- io.quarkus
- quarkus-resteasy
-
-
- io.quarkus
- quarkus-resteasy-jackson
-
-
- io.quarkus
- quarkus-jdbc-postgresql
-
-
- io.quarkus
- quarkus-junit5
- test
-
-
- io.rest-assured
- rest-assured
- test
-
-
-```
-
-### Create the JPA entity
-
-Hibernate ORM with Panache supports the Active Record pattern and the
-Repository pattern to simplify JPA usage. This guide uses the Active Record
-pattern.
-
-Create `Customer.java` by extending `PanacheEntity`. This gives the entity
-built-in persistence methods such as `persist()`, `listAll()`, and
-`findById()`.
-
-```java
-package com.testcontainers.demo;
-
-import io.quarkus.hibernate.orm.panache.PanacheEntity;
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.Table;
-
-@Entity
-@Table(name = "customers")
-public class Customer extends PanacheEntity {
-
- @Column(nullable = false)
- public String name;
-
- @Column(nullable = false, unique = true)
- public String email;
-
- public Customer() {}
-
- public Customer(Long id, String name, String email) {
- this.id = id;
- this.name = name;
- this.email = email;
- }
-}
-```
-
-### Create the CustomerService CDI bean
-
-Create a `CustomerService` class annotated with `@ApplicationScoped` and
-`@Transactional` to handle persistence operations:
-
-```java
-package com.testcontainers.demo;
-
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.transaction.Transactional;
-import java.util.List;
-
-@ApplicationScoped
-@Transactional
-public class CustomerService {
-
- public List getAll() {
- return Customer.listAll();
- }
-
- public Customer create(Customer customer) {
- customer.persist();
- return customer;
- }
-}
-```
-
-### Add the Flyway database migration script
-
-Create `src/main/resources/db/migration/V1__init_database.sql`:
-
-```sql
-create sequence customers_seq start with 1 increment by 50;
-
-create table customers
-(
- id bigint DEFAULT nextval('customers_seq') not null,
- name varchar not null,
- email varchar not null,
- primary key (id)
-);
-
-insert into customers(name, email)
-values ('john', 'john@mail.com'),
- ('rambo', 'rambo@mail.com');
-```
-
-Enable Flyway migrations in `src/main/resources/application.properties`:
-
-```properties
-quarkus.flyway.migrate-at-start=true
-```
-
-### Create the REST API endpoints
-
-Create `CustomerResource.java` with endpoints for fetching all customers and
-creating a customer:
-
-```java
-package com.testcontainers.demo;
-
-import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.Produces;
-import jakarta.ws.rs.core.MediaType;
-import jakarta.ws.rs.core.Response;
-import java.util.List;
-
-@Path("/api/customers")
-@Produces(MediaType.APPLICATION_JSON)
-@Consumes(MediaType.APPLICATION_JSON)
-public class CustomerResource {
- private final CustomerService customerService;
-
- public CustomerResource(CustomerService customerService) {
- this.customerService = customerService;
- }
-
- @GET
- public List getAllCustomers() {
- return customerService.getAll();
- }
-
- @POST
- public Response createCustomer(Customer customer) {
- var savedCustomer = customerService.create(customer);
- return Response.status(Response.Status.CREATED).entity(savedCustomer).build();
- }
-}
-```
-
-## Write tests with Testcontainers
-
-### Quarkus Dev Services
-
-Quarkus Dev Services automatically provisions unconfigured services in
-development and test mode. When you include an extension and don't configure it,
-Quarkus starts the relevant service using
-[Testcontainers](https://www.testcontainers.org/) behind the scenes and wires
-the application to use that service.
-
-> [!NOTE]
-> Dev Services requires a
-> [supported Docker environment](https://www.testcontainers.org/supported_docker_environment/).
-
-Quarkus Dev Services supports most commonly used services like SQL databases,
-Kafka, RabbitMQ, Redis, and MongoDB. For more information, see the
-[Quarkus Dev Services guide](https://quarkus.io/guides/dev-services).
-
-### Write tests for the API endpoints
-
-Test the `GET /api/customers` and `POST /api/customers` endpoints using REST
-Assured. The `io.rest-assured:rest-assured` library was already added as a test
-dependency when you generated the project.
-
-Create `CustomerResourceTest.java` and annotate it with `@QuarkusTest`. This
-bootstraps the application along with the required services using Dev Services.
-Because you haven't configured datasource properties, Dev Services automatically
-starts a PostgreSQL database using Testcontainers.
-
-```java
-package com.testcontainers.demo;
-
-import static io.restassured.RestAssured.given;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-
-import io.quarkus.test.junit.QuarkusTest;
-import io.restassured.common.mapper.TypeRef;
-import io.restassured.http.ContentType;
-import java.util.List;
-import org.junit.jupiter.api.Test;
-
-@QuarkusTest
-class CustomerResourceTest {
-
- @Test
- void shouldGetAllCustomers() {
- List customers = given().when()
- .get("/api/customers")
- .then()
- .statusCode(200)
- .extract()
- .as(new TypeRef<>() {});
- assertFalse(customers.isEmpty());
- }
-
- @Test
- void shouldCreateCustomerSuccessfully() {
- Customer customer = new Customer(null, "John", "john@gmail.com");
- given().contentType(ContentType.JSON)
- .body(customer)
- .when()
- .post("/api/customers")
- .then()
- .statusCode(201)
- .body("name", is("John"))
- .body("email", is("john@gmail.com"));
- }
-}
-```
-
-Here's what the test does:
-
-- `@QuarkusTest` starts the full Quarkus application with Dev Services enabled.
-- Dev Services starts a PostgreSQL container using Testcontainers and configures
- the datasource automatically.
-- `shouldGetAllCustomers()` calls `GET /api/customers` and verifies that seeded
- data from the Flyway migration is returned.
-- `shouldCreateCustomerSuccessfully()` sends a `POST /api/customers` request and
- verifies the response contains the created customer data.
-
-### Customize test configuration
-
-By default, the Quarkus test instance starts on port 8081 and uses a
-`postgres:14` Docker image. Customize both by adding these properties to
-`src/main/resources/application.properties`:
-
-```properties
-quarkus.http.test-port=0
-quarkus.datasource.devservices.image-name=postgres:15.2-alpine
-```
-
-Setting `quarkus.http.test-port=0` starts the application on a random available
-port, avoiding port conflicts. The `devservices.image-name` property lets you
-pin the PostgreSQL image to a specific version that matches production.
-
-### Test with services not supported by Dev Services
-
-Your application might use a service that Dev Services doesn't support out of
-the box. In that case, use `QuarkusTestResourceLifecycleManager` to start the
-service before the Quarkus application starts for testing.
-
-For example, suppose the application uses CockroachDB. First, add the
-CockroachDB Testcontainers module dependency:
-
-```xml
-
- org.testcontainers
- cockroachdb
- test
-
-```
-
-Create a `CockroachDBTestResource` that implements
-`QuarkusTestResourceLifecycleManager`:
-
-```java
-package com.testcontainers.demo;
-
-import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
-import java.util.HashMap;
-import java.util.Map;
-import org.testcontainers.containers.CockroachContainer;
-
-public class CockroachDBTestResource implements QuarkusTestResourceLifecycleManager {
-
- CockroachContainer cockroachdb;
-
- @Override
- public Map start() {
- cockroachdb = new CockroachContainer("cockroachdb/cockroach:v22.2.0");
- cockroachdb.start();
- Map conf = new HashMap<>();
- conf.put("quarkus.datasource.jdbc.url", cockroachdb.getJdbcUrl());
- conf.put("quarkus.datasource.username", cockroachdb.getUsername());
- conf.put("quarkus.datasource.password", cockroachdb.getPassword());
- return conf;
- }
-
- @Override
- public void stop() {
- cockroachdb.stop();
- }
-}
-```
-
-Use the `CockroachDBTestResource` with `@QuarkusTestResource` in a test class:
-
-```java
-package com.testcontainers.demo;
-
-import static io.restassured.RestAssured.given;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-
-import io.quarkus.test.common.QuarkusTestResource;
-import io.quarkus.test.junit.QuarkusTest;
-import io.restassured.common.mapper.TypeRef;
-import java.util.List;
-import org.junit.jupiter.api.Test;
-
-@QuarkusTest
-@QuarkusTestResource(value = CockroachDBTestResource.class, restrictToAnnotatedClass = true)
-class CockroachDBTest {
-
- @Test
- void shouldGetAllCustomers() {
- List customers = given().when()
- .get("/api/customers")
- .then()
- .statusCode(200)
- .extract()
- .as(new TypeRef<>() {});
- assertFalse(customers.isEmpty());
- }
-}
-```
-
-The `restrictToAnnotatedClass = true` attribute ensures the CockroachDB
-container only starts when running this specific test class, rather than being
-activated for all tests.
-
-## Run tests and next steps
-
-### Run the tests
-
-```console
-$ ./mvnw test
-```
-
-Or with Gradle:
-
-```console
-$ ./gradlew test
-```
-
-You should see the PostgreSQL Docker container start and all tests pass. After
-the tests finish, the container stops and is removed automatically.
-
-### Run the application locally
-
-Quarkus Dev Services automatically provisions unconfigured services in
-development mode. Start the Quarkus application in dev mode:
-
-```console
-$ ./mvnw compile quarkus:dev
-```
-
-Or with Gradle:
-
-```console
-$ ./gradlew quarkusDev
-```
-
-Dev Services starts a PostgreSQL container automatically. If you're running a
-PostgreSQL database on your system and want to use that instead, configure the
-datasource properties in `src/main/resources/application.properties`:
-
-```properties
-quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/postgres
-quarkus.datasource.username=postgres
-quarkus.datasource.password=postgres
-```
-
-When these properties are set explicitly, Dev Services doesn't provision the
-database container and instead connects to the configured database.
-
-### Summary
-
-Quarkus Dev Services improves the developer experience by automatically
-provisioning the required services using Testcontainers during development and
-testing. This guide covered:
-
-- Building a REST API using JAX-RS with Hibernate ORM with Panache
-- Testing API endpoints using REST Assured with Dev Services handling database
- provisioning
-- Using `QuarkusTestResourceLifecycleManager` for services not supported by Dev
- Services
-- Running the application locally with Dev Services
-
-To learn more about Testcontainers, visit the
-[Testcontainers overview](https://testcontainers.com/getting-started/).
-
-### Further reading
-
-- [Quarkus Dev Services overview](https://quarkus.io/guides/dev-services)
-- [Quarkus testing guide](https://quarkus.io/guides/getting-started-testing)
-- [Testcontainers Postgres module](https://java.testcontainers.org/modules/databases/postgres/)
diff --git a/content/guides/testcontainers-java-spring-boot-kafka.md b/content/guides/testcontainers-java-spring-boot-kafka.md
deleted file mode 100644
index 203db79898..0000000000
--- a/content/guides/testcontainers-java-spring-boot-kafka.md
+++ /dev/null
@@ -1,471 +0,0 @@
----
-title: Testing Spring Boot Kafka Listener using Testcontainers
-linkTitle: Spring Boot Kafka
-description: Learn how to test a Spring Boot Kafka listener using Testcontainers with Kafka and MySQL modules.
-keywords: testcontainers, java, spring boot, testing, kafka, mysql, jpa, awaitility
-summary: |
- Learn how to create a Spring Boot application with a Kafka listener that persists data in MySQL,
- then test it using Testcontainers Kafka and MySQL modules with Awaitility.
-aliases:
- - /guides/testcontainers-java-spring-boot-kafka/create-project/
- - /guides/testcontainers-java-spring-boot-kafka/run-tests/
- - /guides/testcontainers-java-spring-boot-kafka/write-tests/
-params:
- tags: [testing]
- time: 25 minutes
----
-
-
-
-
-In this guide, you will learn how to:
-
-- Create a Spring Boot application with Kafka integration
-- Implement a Kafka listener and persist data in a MySQL database
-- Test the Kafka listener using Testcontainers and Awaitility
-
-## Prerequisites
-
-- Java 17+
-- Maven or Gradle
-- A Docker environment supported by Testcontainers
-
-> [!NOTE]
-> If you're new to Testcontainers, visit the
-> [Testcontainers overview](https://testcontainers.com/getting-started/) to learn more about
-> Testcontainers and the benefits of using it.
-
-## Create the Spring Boot project
-
-### Set up the project
-
-Create a Spring Boot project from [Spring Initializr](https://start.spring.io)
-by selecting the **Spring for Apache Kafka**, **Spring Data JPA**, **MySQL Driver**,
-and **Testcontainers** starters.
-
-Alternatively, clone the
-[guide repository](https://github.com/testcontainers/tc-guide-testing-spring-boot-kafka-listener).
-
-After generating the application, add the Awaitility library as a test
-dependency. You'll use it later to assert the expectations of an asynchronous
-process flow.
-
-The key dependencies in `pom.xml` are:
-
-```xml
-
- 17
- 2.0.4
-
-
-
- org.springframework.boot
- spring-boot-starter-data-jpa
-
-
- org.springframework.kafka
- spring-kafka
-
-
- com.mysql
- mysql-connector-j
- runtime
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.springframework.kafka
- spring-kafka-test
- test
-
-
- org.testcontainers
- testcontainers-junit-jupiter
- test
-
-
- org.testcontainers
- testcontainers-kafka
- test
-
-
- org.testcontainers
- testcontainers-mysql
- test
-
-
- org.awaitility
- awaitility
- test
-
-
-```
-
-Using the Testcontainers BOM (Bill of Materials) is recommended so that you
-don't have to repeat the version for every Testcontainers module dependency.
-
-### Create the JPA entity
-
-The application listens to a topic called `product-price-changes`. When a
-message arrives, it extracts the product code and price from the event payload
-and updates the price for that product in the MySQL database.
-
-Create `Product.java`:
-
-```java
-package com.testcontainers.demo;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.Table;
-import java.math.BigDecimal;
-
-@Entity
-@Table(name = "products")
-class Product {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
-
- @Column(nullable = false, unique = true)
- private String code;
-
- @Column(nullable = false)
- private String name;
-
- @Column(nullable = false)
- private BigDecimal price;
-
- public Product() {}
-
- public Product(Long id, String code, String name, BigDecimal price) {
- this.id = id;
- this.code = code;
- this.name = name;
- this.price = price;
- }
-
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getCode() {
- return code;
- }
-
- public void setCode(String code) {
- this.code = code;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-
- public BigDecimal getPrice() {
- return price;
- }
-
- public void setPrice(BigDecimal price) {
- this.price = price;
- }
-}
-```
-
-### Create the Spring Data JPA repository
-
-Create a repository interface for the `Product` entity with a method to find a
-product by code and a method to update the price for a given product code:
-
-```java
-package com.testcontainers.demo;
-
-import java.math.BigDecimal;
-import java.util.Optional;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Modifying;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.repository.query.Param;
-
-interface ProductRepository extends JpaRepository {
- Optional findByCode(String code);
-
- @Modifying
- @Query("update Product p set p.price = :price where p.code = :productCode")
- void updateProductPrice(
- @Param("productCode") String productCode,
- @Param("price") BigDecimal price
- );
-}
-```
-
-### Add a schema creation script
-
-Because the application doesn't use an in-memory database, you need to create
-the MySQL tables. The recommended approach for production is a migration tool
-like Flyway or Liquibase, but for this guide the built-in Spring Boot schema
-initialization is sufficient.
-
-Create `src/main/resources/schema.sql`:
-
-```sql
-create table products (
- id int NOT NULL AUTO_INCREMENT,
- code varchar(255) not null,
- name varchar(255) not null,
- price numeric(5,2) not null,
- PRIMARY KEY (id),
- UNIQUE (code)
-);
-```
-
-Enable schema initialization in `src/main/resources/application.properties`:
-
-```properties
-spring.sql.init.mode=always
-```
-
-### Create the event payload
-
-Create a record named `ProductPriceChangedEvent` that represents the structure
-of the event payload received from the Kafka topic:
-
-```java
-package com.testcontainers.demo;
-
-import java.math.BigDecimal;
-
-record ProductPriceChangedEvent(String productCode, BigDecimal price) {}
-```
-
-The sender and receiver agree on the following JSON format:
-
-```json
-{
- "productCode": "P100",
- "price": 25.0
-}
-```
-
-### Implement the Kafka listener
-
-Create `ProductPriceChangedEventHandler.java`, which handles messages from the
-`product-price-changes` topic and updates the product price in the database:
-
-```java
-package com.testcontainers.demo;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.kafka.annotation.KafkaListener;
-import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Transactional;
-
-@Component
-@Transactional
-class ProductPriceChangedEventHandler {
-
- private static final Logger log = LoggerFactory.getLogger(
- ProductPriceChangedEventHandler.class
- );
-
- private final ProductRepository productRepository;
-
- ProductPriceChangedEventHandler(ProductRepository productRepository) {
- this.productRepository = productRepository;
- }
-
- @KafkaListener(topics = "product-price-changes", groupId = "demo")
- public void handle(ProductPriceChangedEvent event) {
- log.info(
- "Received a ProductPriceChangedEvent with productCode:{}: ",
- event.productCode()
- );
- productRepository.updateProductPrice(event.productCode(), event.price());
- }
-}
-```
-
-The `@KafkaListener` annotation specifies the topic name to listen to. Spring
-Kafka handles serialization and deserialization based on the properties
-configured in `application.properties`.
-
-### Configure Kafka serialization
-
-Add the following Kafka properties to
-`src/main/resources/application.properties`:
-
-```properties
-######## Kafka Configuration #########
-spring.kafka.bootstrap-servers=localhost:9092
-spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer
-spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
-
-spring.kafka.consumer.group-id=demo
-spring.kafka.consumer.auto-offset-reset=latest
-spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer
-spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
-spring.kafka.consumer.properties.spring.json.trusted.packages=com.testcontainers.demo
-```
-
-The `productCode` key is (de)serialized using `StringSerializer`/`StringDeserializer`,
-and the `ProductPriceChangedEvent` value is (de)serialized using
-`JsonSerializer`/`JsonDeserializer`.
-
-## Write tests with Testcontainers
-
-To test the Kafka listener, you need a running Kafka broker and a MySQL
-database, plus a started Spring context. Testcontainers spins up both services
-in Docker containers and `@DynamicPropertySource` connects them to Spring.
-
-### Write the test
-
-Create `ProductPriceChangedEventHandlerTest.java`:
-
-```java
-package com.testcontainers.demo;
-
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.awaitility.Awaitility.await;
-
-import java.math.BigDecimal;
-import java.time.Duration;
-import java.util.Optional;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.kafka.core.KafkaTemplate;
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
-import org.springframework.test.context.TestPropertySource;
-import org.testcontainers.kafka.ConfluentKafkaContainer;
-import org.testcontainers.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
-
-@SpringBootTest
-@TestPropertySource(
- properties = {
- "spring.kafka.consumer.auto-offset-reset=earliest",
- "spring.datasource.url=jdbc:tc:mysql:8.0.32:///db",
- }
-)
-@Testcontainers
-class ProductPriceChangedEventHandlerTest {
-
- @Container
- static final ConfluentKafkaContainer kafka =
- new ConfluentKafkaContainer("confluentinc/cp-kafka:7.8.0");
-
- @DynamicPropertySource
- static void overrideProperties(DynamicPropertyRegistry registry) {
- registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
- }
-
- @Autowired
- private KafkaTemplate kafkaTemplate;
-
- @Autowired
- private ProductRepository productRepository;
-
- @BeforeEach
- void setUp() {
- Product product = new Product(null, "P100", "Product One", BigDecimal.TEN);
- productRepository.save(product);
- }
-
- @Test
- void shouldHandleProductPriceChangedEvent() {
- ProductPriceChangedEvent event = new ProductPriceChangedEvent(
- "P100",
- new BigDecimal("14.50")
- );
-
- kafkaTemplate.send("product-price-changes", event.productCode(), event);
-
- await()
- .pollInterval(Duration.ofSeconds(3))
- .atMost(10, SECONDS)
- .untilAsserted(() -> {
- Optional optionalProduct = productRepository.findByCode(
- "P100"
- );
- assertThat(optionalProduct).isPresent();
- assertThat(optionalProduct.get().getCode()).isEqualTo("P100");
- assertThat(optionalProduct.get().getPrice())
- .isEqualTo(new BigDecimal("14.50"));
- });
- }
-}
-```
-
-Here's what the test does:
-
-- `@SpringBootTest` starts the full Spring application context.
-- The Testcontainers special JDBC URL (`jdbc:tc:mysql:8.0.32:///db`) in
- `@TestPropertySource` spins up a MySQL container and configures it as the
- datasource automatically.
-- `@Testcontainers` and `@Container` manage the lifecycle of the Kafka
- container. `@DynamicPropertySource` registers the Kafka bootstrap servers
- with Spring so that the producer and consumer connect to the test container.
-- `@BeforeEach` creates a `Product` record in the database before each test.
-- The test sends a `ProductPriceChangedEvent` to the `product-price-changes`
- topic using `KafkaTemplate`. Spring Boot converts the object to JSON using
- `JsonSerializer`.
-- Because Kafka message processing is asynchronous, the test uses
- [Awaitility](http://www.awaitility.org/) to poll every 3 seconds (up to a
- maximum of 10 seconds) until the product price in the database matches the
- expected value.
-- The property `spring.kafka.consumer.auto-offset-reset` is set to `earliest`
- so that the listener consumes messages even if they're sent to the topic
- before the listener is ready. This setting is helpful when running tests.
-
-## Run tests and next steps
-
-### Run the tests
-
-```console
-$ ./mvnw test
-```
-
-Or with Gradle:
-
-```console
-$ ./gradlew test
-```
-
-You should see the Kafka and MySQL Docker containers start and all tests pass.
-After the tests finish, the containers stop and are removed automatically.
-
-### Summary
-
-Testing with real Kafka and MySQL instances gives you more confidence in the
-correctness of your code than mocks or embedded alternatives. The
-Testcontainers library manages the container lifecycle so that your integration
-tests run against the same services you use in production.
-
-To learn more about Testcontainers, visit the
-[Testcontainers overview](https://testcontainers.com/getting-started/).
-
-### Further reading
-
-- [Getting started with Testcontainers in a Java Spring Boot project](https://testcontainers.com/guides/testing-spring-boot-rest-api-using-testcontainers/)
-- [The simplest way to replace H2 with a real database for testing](https://testcontainers.com/guides/replace-h2-with-real-database-for-testing/)
-- [Awaitility](http://www.awaitility.org/)
-- [Testcontainers Kafka module](https://java.testcontainers.org/modules/kafka/)
-- [Testcontainers MySQL module](https://java.testcontainers.org/modules/databases/mysql/)
diff --git a/content/guides/testcontainers-java-wiremock.md b/content/guides/testcontainers-java-wiremock.md
deleted file mode 100644
index 536683ddc4..0000000000
--- a/content/guides/testcontainers-java-wiremock.md
+++ /dev/null
@@ -1,770 +0,0 @@
----
-title: Testing REST API integrations using WireMock
-linkTitle: WireMock
-description: Learn how to test REST API integrations in a Spring Boot application using the Testcontainers WireMock module.
-keywords: testcontainers, java, spring boot, testing, wiremock, rest api, rest assured
-summary: |
- Learn how to create a Spring Boot application that integrates with
- external REST APIs, then test those integrations using Testcontainers
- and WireMock.
-aliases:
- - /guides/testcontainers-java-wiremock/create-project/
- - /guides/testcontainers-java-wiremock/run-tests/
- - /guides/testcontainers-java-wiremock/write-tests/
-params:
- tags: [testing]
- time: 20 minutes
----
-
-
-
-
-In this guide, you'll learn how to:
-
-- Create a Spring Boot application that talks to external REST APIs
-- Test external API integrations using WireMock with both the JUnit 5 extension
- and the Testcontainers WireMock module
-
-## Prerequisites
-
-- Java 17+
-- Maven or Gradle
-- A Docker environment supported by Testcontainers
-
-> [!NOTE]
-> If you're new to Testcontainers, visit the
-> [Testcontainers overview](https://testcontainers.com/getting-started/) to learn more about
-> Testcontainers and the benefits of using it.
-
-## Create the Spring Boot project
-
-### Set up the project
-
-Create a Spring Boot project from [Spring Initializr](https://start.spring.io)
-by selecting the **Spring Web** and **Testcontainers** starters.
-
-Alternatively, clone the
-[guide repository](https://github.com/testcontainers/tc-guide-testing-rest-api-integrations-using-wiremock).
-
-After generating the project, add the **REST Assured**, **WireMock**, and
-**WireMock Testcontainers module** libraries as test dependencies. The key
-dependencies in `pom.xml` are:
-
-```xml
-
- 17
- 2.0.4
- 1.0-alpha-13
-
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
- org.testcontainers
- testcontainers-junit-jupiter
- test
-
-
- org.wiremock
- wiremock-standalone
- 3.6.0
- test
-
-
- org.wiremock.integrations.testcontainers
- wiremock-testcontainers-module
- ${wiremock-testcontainers.version}
- test
-
-
- io.rest-assured
- rest-assured
- test
-
-
-```
-
-Using the Testcontainers BOM (Bill of Materials) is recommended so that you
-don't have to repeat the version for every Testcontainers module dependency.
-
-This guide builds an application that manages video albums. A third-party REST
-API handles photo assets. For demonstration purposes, the application uses the
-publicly available [JSONPlaceholder](https://jsonplaceholder.typicode.com/) API
-as a photo service.
-
-The application exposes a `GET /api/albums/{albumId}` endpoint that calls the
-photo service to fetch photos for a given album.
-[WireMock](https://wiremock.org/) is a tool for building mock APIs.
-Testcontainers provides a
-[WireMock module](https://testcontainers.com/modules/wiremock/) that runs
-WireMock as a Docker container.
-
-### Create the Album and Photo models
-
-Create `Album.java` using Java records:
-
-```java
-package com.testcontainers.demo;
-
-import java.util.List;
-
-public record Album(Long albumId, List photos) {}
-
-record Photo(Long id, String title, String url, String thumbnailUrl) {}
-```
-
-### Create the PhotoServiceClient
-
-Create `PhotoServiceClient.java`, which uses `RestTemplate` to fetch photos for
-a given album ID:
-
-```java
-package com.testcontainers.demo;
-
-import java.util.List;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.web.client.RestTemplateBuilder;
-import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.http.HttpMethod;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-import org.springframework.web.client.RestTemplate;
-
-@Service
-class PhotoServiceClient {
-
- private final String baseUrl;
- private final RestTemplate restTemplate;
-
- PhotoServiceClient(
- @Value("${photos.api.base-url}") String baseUrl,
- RestTemplateBuilder builder
- ) {
- this.baseUrl = baseUrl;
- this.restTemplate = builder.build();
- }
-
- List getPhotos(Long albumId) {
- String url = baseUrl + "/albums/{albumId}/photos";
- ResponseEntity> response = restTemplate.exchange(
- url,
- HttpMethod.GET,
- null,
- new ParameterizedTypeReference<>() {},
- albumId
- );
- return response.getBody();
- }
-}
-```
-
-The photo service base URL is externalized as a configuration property. Add the
-following entry to `src/main/resources/application.properties`:
-
-```properties
-photos.api.base-url=https://jsonplaceholder.typicode.com
-```
-
-### Create the REST API endpoint
-
-Create `AlbumController.java`:
-
-```java
-package com.testcontainers.demo;
-
-import java.util.List;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.client.RestClientResponseException;
-
-@RestController
-@RequestMapping("/api")
-class AlbumController {
-
- private static final Logger logger = LoggerFactory.getLogger(
- AlbumController.class
- );
-
- private final PhotoServiceClient photoServiceClient;
-
- AlbumController(PhotoServiceClient photoServiceClient) {
- this.photoServiceClient = photoServiceClient;
- }
-
- @GetMapping("/albums/{albumId}")
- public ResponseEntity getAlbumById(@PathVariable Long albumId) {
- try {
- List photos = photoServiceClient.getPhotos(albumId);
- return ResponseEntity.ok(new Album(albumId, photos));
- } catch (RestClientResponseException e) {
- logger.error("Failed to get photos", e);
- return new ResponseEntity<>(e.getStatusCode());
- }
- }
-}
-```
-
-This endpoint calls the photo service for a given album ID and returns a
-response like:
-
-```json
-{
- "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"
- }
- ]
-}
-```
-
-## Write tests with WireMock and Testcontainers
-
-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.
-
-### Test using WireMock JUnit 5 extension
-
-WireMock provides a JUnit 5 extension that starts an in-process WireMock server.
-You can configure stub responses using the WireMock Java API.
-
-Create `AlbumControllerTest.java`:
-
-```java
-package com.testcontainers.demo;
-
-import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
-import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
-import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
-import static io.restassured.RestAssured.given;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.Matchers.hasSize;
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-import com.github.tomakehurst.wiremock.client.WireMock;
-import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
-import io.restassured.RestAssured;
-import io.restassured.http.ContentType;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.web.server.LocalServerPort;
-import org.springframework.http.MediaType;
-import org.springframework.test.context.DynamicPropertyRegistry;
-import org.springframework.test.context.DynamicPropertySource;
-
-@SpringBootTest(webEnvironment = RANDOM_PORT)
-class AlbumControllerTest {
-
- @LocalServerPort
- private Integer port;
-
- @RegisterExtension
- static WireMockExtension wireMock = WireMockExtension
- .newInstance()
- .options(wireMockConfig().dynamicPort())
- .build();
-
- @DynamicPropertySource
- static void configureProperties(DynamicPropertyRegistry registry) {
- registry.add("photos.api.base-url", wireMock::baseUrl);
- }
-
- @BeforeEach
- void setUp() {
- RestAssured.port = port;
- }
-
- @Test
- void shouldGetAlbumById() {
- Long albumId = 1L;
-
- wireMock.stubFor(
- WireMock
- .get(urlMatching("/albums/" + albumId + "/photos"))
- .willReturn(
- aResponse()
- .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
- .withBody(
- """
- [
- {
- "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));
- }
-
- @Test
- void shouldReturnServerErrorWhenPhotoServiceCallFailed() {
- Long albumId = 2L;
- wireMock.stubFor(
- WireMock
- .get(urlMatching("/albums/" + albumId + "/photos"))
- .willReturn(aResponse().withStatus(500))
- );
-
- given()
- .contentType(ContentType.JSON)
- .when()
- .get("/api/albums/{albumId}", albumId)
- .then()
- .statusCode(500);
- }
-}
-```
-
-Here's what the test does:
-
-- `@SpringBootTest` starts the full application on a random port.
-- `@RegisterExtension` creates a `WireMockExtension` that starts WireMock on a
- dynamic port.
-- `@DynamicPropertySource` overrides `photos.api.base-url` to point at the
- WireMock endpoint, so the application talks to WireMock instead of the real
- photo service.
-- `shouldGetAlbumById()` configures a stub response for
- `/albums/{albumId}/photos`, sends a request to the application's
- `/api/albums/{albumId}` endpoint, and verifies the response body.
-- `shouldReturnServerErrorWhenPhotoServiceCallFailed()` configures WireMock to
- return a 500 status and verifies that the application propagates that status to
- the caller.
-
-### Stub using JSON mapping files
-
-Instead of using the WireMock Java API, you can configure stubs with JSON
-mapping files. Create
-`src/test/resources/wiremock/mappings/get-album-photos.json`:
-
-```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": []
- }
- }
- ]
-}
-```
-
-Create the response body file at
-`src/test/resources/wiremock/__files/album-photos-resp-200.json`:
-
-```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"
- }
-]
-```
-
-Initialize WireMock to load stubs from the mapping files:
-
-```java
-@RegisterExtension
-static WireMockExtension wireMockServer = WireMockExtension
- .newInstance()
- .options(
- wireMockConfig().dynamicPort().usingFilesUnderClasspath("wiremock")
- )
- .build();
-```
-
-With mapping-based stubs in place, create
-`AlbumControllerWireMockMappingTests.java`:
-
-```java
-package com.testcontainers.demo;
-
-import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
-import static io.restassured.RestAssured.given;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.Matchers.hasSize;
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
-import io.restassured.RestAssured;
-import io.restassured.http.ContentType;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.RegisterExtension;
-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;
-
-@SpringBootTest(webEnvironment = RANDOM_PORT)
-class AlbumControllerWireMockMappingTests {
-
- @LocalServerPort
- private Integer port;
-
- @RegisterExtension
- static WireMockExtension wireMockServer = WireMockExtension
- .newInstance()
- .options(
- wireMockConfig().dynamicPort().usingFilesUnderClasspath("wiremock")
- )
- .build();
-
- @DynamicPropertySource
- static void configureProperties(DynamicPropertyRegistry registry) {
- registry.add("photos.api.base-url", wireMockServer::baseUrl);
- }
-
- @BeforeEach
- void setUp() {
- RestAssured.port = port;
- }
-
- @Test
- void shouldGetAlbumById() {
- Long albumId = 1L;
-
- given()
- .contentType(ContentType.JSON)
- .when()
- .get("/api/albums/{albumId}", albumId)
- .then()
- .statusCode(200)
- .body("albumId", is(albumId.intValue()))
- .body("photos", hasSize(2));
- }
-
- @Test
- void shouldReturnServerErrorWhenPhotoServiceCallFailed() {
- Long albumId = 2L;
-
- given()
- .contentType(ContentType.JSON)
- .when()
- .get("/api/albums/{albumId}", albumId)
- .then()
- .statusCode(500);
- }
-
- @Test
- void shouldReturnEmptyPhotos() {
- Long albumId = 3L;
-
- given()
- .contentType(ContentType.JSON)
- .when()
- .get("/api/albums/{albumId}", albumId)
- .then()
- .statusCode(200)
- .body("albumId", is(albumId.intValue()))
- .body("photos", hasSize(0));
- }
-}
-```
-
-The tests don't need inline stub definitions because WireMock loads the mappings
-automatically from the classpath.
-
-### Test using the Testcontainers WireMock module
-
-The [Testcontainers WireMock module](https://testcontainers.com/modules/wiremock/)
-provisions WireMock as a standalone Docker container, based on
-[WireMock Docker](https://github.com/wiremock/wiremock-docker). This approach is
-useful when you want complete isolation between the test JVM and the mock server.
-
-Create a mock configuration file at
-`src/test/resources/com/testcontainers/demo/AlbumControllerTestcontainersTests/mocks-config.json`:
-
-```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": []
- }
- }
- ]
-}
-```
-
-Create the response body file at
-`src/test/resources/com/testcontainers/demo/AlbumControllerTestcontainersTests/album-photos-response.json`:
-
-```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"
- }
-]
-```
-
-Create `AlbumControllerTestcontainersTests.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.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-
-import io.restassured.RestAssured;
-import io.restassured.http.ContentType;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-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.junit.jupiter.Container;
-import org.testcontainers.junit.jupiter.Testcontainers;
-import org.wiremock.integrations.testcontainers.WireMockContainer;
-
-@SpringBootTest(webEnvironment = RANDOM_PORT)
-@Testcontainers
-class AlbumControllerTestcontainersTests {
-
- @LocalServerPort
- private Integer port;
-
- @Container
- static WireMockContainer wiremockServer = new WireMockContainer(
- "wiremock/wiremock:3.6.0"
- )
- .withMapping(
- "photos-by-album",
- AlbumControllerTestcontainersTests.class,
- "mocks-config.json"
- )
- .withFileFromResource(
- "album-photos-response.json",
- AlbumControllerTestcontainersTests.class,
- "album-photos-response.json"
- );
-
- @DynamicPropertySource
- static void configureProperties(DynamicPropertyRegistry registry) {
- registry.add("photos.api.base-url", wiremockServer::getBaseUrl);
- }
-
- @BeforeEach
- void setUp() {
- RestAssured.port = port;
- }
-
- @Test
- void shouldGetAlbumById() {
- Long albumId = 1L;
-
- given()
- .contentType(ContentType.JSON)
- .when()
- .get("/api/albums/{albumId}", albumId)
- .then()
- .statusCode(200)
- .body("albumId", is(albumId.intValue()))
- .body("photos", hasSize(2));
- }
-
- @Test
- void shouldReturnServerErrorWhenPhotoServiceCallFailed() {
- Long albumId = 2L;
-
- given()
- .contentType(ContentType.JSON)
- .when()
- .get("/api/albums/{albumId}", albumId)
- .then()
- .statusCode(500);
- }
-
- @Test
- void shouldReturnEmptyPhotos() {
- Long albumId = 3L;
-
- given()
- .contentType(ContentType.JSON)
- .when()
- .get("/api/albums/{albumId}", albumId)
- .then()
- .statusCode(200)
- .body("albumId", is(albumId.intValue()))
- .body("photos", hasSize(0));
- }
-}
-```
-
-Here's what the test does:
-
-- The `@Testcontainers` and `@Container` annotations start a
- `WireMockContainer` using the `wiremock/wiremock:3.6.0` Docker image.
-- `withMapping()` loads stub mappings from `mocks-config.json`, and
- `withFileFromResource()` loads the response body file.
-- `@DynamicPropertySource` overrides `photos.api.base-url` to point at the
- WireMock container's base URL.
-- The tests don't contain inline stub definitions because WireMock loads them
- from the JSON configuration files.
-
-## Run tests and next steps
-
-### Run the tests
-
-```console
-$ ./mvnw test
-```
-
-Or with Gradle:
-
-```console
-$ ./gradlew test
-```
-
-You should see the WireMock Docker container start in the console output. It
-acts as the photo service, serving mock responses based on the configured
-expectations. All tests should pass.
-
-### Summary
-
-You built a Spring Boot application that integrates with an external REST API,
-then tested that integration using three different approaches:
-
-- WireMock JUnit 5 extension with inline stubs
-- WireMock JUnit 5 extension with JSON mapping files
-- Testcontainers WireMock module running WireMock in a Docker container
-
-Testing at the HTTP protocol level instead of mocking Java methods lets you
-catch serialization issues and simulate realistic failure scenarios.
-
-To learn more about Testcontainers, visit the
-[Testcontainers overview](https://testcontainers.com/getting-started/).
-
-### Further reading
-
-- [Testcontainers WireMock module](https://testcontainers.com/modules/wiremock/)
-- [WireMock documentation](https://wiremock.org/docs/)
-- [Testcontainers JUnit 5 quickstart](https://java.testcontainers.org/quickstart/junit_5_quickstart/)
diff --git a/content/manuals/testcontainers.md b/content/manuals/testcontainers.md
index 3107688a04..0f69fecf18 100644
--- a/content/manuals/testcontainers.md
+++ b/content/manuals/testcontainers.md
@@ -74,12 +74,3 @@ with different languages and popular frameworks:
- [Replace H2 with a real database for testing](/guides/testcontainers-java-replace-h2/)
- [Configuration of services running in a container](/guides/testcontainers-java-service-configuration/)
- [Testing an ASP.NET Core web app](/guides/testcontainers-dotnet-aspnet-core/)
-- [Testing Spring Boot Kafka Listener](/guides/testcontainers-java-spring-boot-kafka/)
-- [Testing REST API integrations using MockServer](/guides/testcontainers-java-mockserver/)
-- [Testing AWS service integrations using LocalStack](/guides/testcontainers-java-aws-localstack/)
-- [Testing Quarkus applications with Testcontainers](/guides/testcontainers-java-quarkus/)
-- [Working with jOOQ and Flyway using Testcontainers](/guides/testcontainers-java-jooq-flyway/)
-- [Testing REST API integrations using WireMock](/guides/testcontainers-java-wiremock/)
-- [Securing Spring Boot with Keycloak and Testcontainers](/guides/testcontainers-java-keycloak-spring-boot/)
-- [Testing Micronaut REST API with WireMock](/guides/testcontainers-java-micronaut-wiremock/)
-- [Testing Micronaut Kafka Listener](/guides/testcontainers-java-micronaut-kafka/)