Skip to main content
reactive microservices architecture transactional outbox and event sourcing with java 21

Integration Testing with Testcontainers

3 min read Chapter 10 of 10
Summary

The draft discusses deterministic testing with Testcontainers and...

The draft discusses deterministic testing with Testcontainers and the Transactional Outbox pattern.

Introduction to Deterministic Testing with Testcontainers

Building on the concepts of reliability and fault tolerance discussed previously, this section delves into the implementation of deterministic testing using Testcontainers. Deterministic testing is a crucial approach that ensures, for a given input, the system always produces the same output. This is particularly important in distributed systems where the complexity of interactions between components can lead to unpredictable behavior.

The Role of Testcontainers in Deterministic Testing

Testcontainers is an open-source library that provides lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container for integration testing. By utilizing Testcontainers, developers can eliminate shared environments and manual setup, which are common barriers to achieving deterministic testing. The library automatically manages the lifecycle of Docker-based services, allowing for the creation of isolated, disposable environments for every test run.

Implementing Transactional Outbox with Testcontainers

The Transactional Outbox pattern is a design approach that ensures atomicity between database state changes and message publishing. This pattern is crucial for preventing the ‘Dual Write Problem,’ a consistency issue in distributed systems where an application fails to update a database and a message broker simultaneously. By integrating Testcontainers with the Transactional Outbox pattern, developers can create comprehensive tests that verify the consistency and reliability of their system.

@Testcontainers
class OutboxIntegrationTest {
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
    
    @Container
    static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.0"));

    @DynamicPropertySource
    static void properties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
    }

    @Test
    void shouldCommitToOutboxAndArriveInKafka() {
        // 1. Transactional Service Call (DB Update + Outbox Insert)
        orderService.placeOrder(new OrderRequest("SKU-123", 1));
        
        // 2. Restart Kafka to verify ordering/resilience
        kafka.stop();
        kafka.start();

        // 3. Verify Kafka Arrival via Consumer
        Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> {
            List<OrderEvent> events = kafkaConsumer.readEvents();
            assertThat(events).hasSize(1);
            assertThat(events.get(0).sku()).isEqualTo("SKU-123");
        });
    }
}

## Outbox Table Schema
The standard Outbox table schema includes the following columns:

| Outbox Column | Data Type | Description |
| --- | --- | --- |
| id | UUID | Primary Key |
| aggregate_id | VARCHAR | ID of the source entity |
| event_type | VARCHAR | Type of event (e.g., OrderCreated) |
| payload | JSONB | Serialized event data |
| created_at | TIMESTAMP | Audit/Ordering timestamp |

## Performance Impact
The use of Testcontainers and the Transactional Outbox pattern can have a significant performance impact on the system. The creation of isolated environments for every test run can lead to increased resource utilization, and the atomicity ensured by the Transactional Outbox pattern can introduce additional latency. However, these trade-offs are necessary to ensure the reliability and consistency of the system.

## Sources
[1] https://www.javacodegeeks.com/2025/07/modern-java-testing-with-junit-5-and-testcontainers.html
[2] https://java.testcontainers.org/modules/kafka/
[3] https://www.codecentric.de/en/knowledge-hub/blog/archunit-in-practice-keep-your-architecture-clean