rgoussu@goussu: ~/library/java/testing
~/library/java/testing cat integration-testing.md

Integration testing — Testcontainers & framework test support

# Testing against real dependencies with Testcontainers, Spring test slices, Quarkus Dev Services, Micronaut Test and WireMock.

Conceptsaved 2026-08-09 #java#testing#testcontainers#frameworks#storage

Overview

Integration tests occupy the middle of the portfolio: your code plus its real dependencies — database, message broker, HTTP peer — but still run from the build, not a deployed environment. Testcontainers changed the economics of this layer: a real Postgres or Kafka in Docker per test run beats an in-memory imitation (H2 is not Postgres) at little extra cost. Spring, Quarkus and Micronaut each ship test support that boots the framework context and, increasingly, wires the containers for you.

Key points

  • Test against the real engine: an H2 test passing proves H2 compatibility; dialect quirks, locking behaviour and JSON/array columns only show up on the real database.
  • Testcontainers is the centerpiece: programmatic Docker containers with lifecycle tied to the test run, ready-made modules (postgres, kafka, mongodb, redis, localstack, …) and automatic port mapping.
  • Container reuse (testcontainers.reuse.enable=true + .withReuse(true)) keeps a container alive across runs — seconds instead of tens of seconds per local iteration.
  • Spring slices load a fraction of the context: @DataJpaTest (JPA layer), @WebMvcTest (controllers + MockMvc), @JsonTest, @RestClientTest; full @SpringBootTest only when wiring itself is under test.
  • Quarkus Dev Services: @QuarkusTest boots the app; unconfigured datasources/brokers are auto-provisioned as Testcontainers behind the scenes — zero test config.
  • Micronaut: @MicronautTest starts the context (fast, thanks to compile-time DI) and supports @MockBean replacements and Test Resources for container provisioning.
  • WireMock stubs external HTTP: verify your client code against a real HTTP server with canned responses, faults and delays — never mock your own HTTP client classes.
  • Migrations run in tests: apply Flyway/Liquibase against the container so the schema under test is the schema in production.

Details

Testcontainers patterns

  • Per-class static container with @Testcontainers/@Container on a static field: one container for all tests in the class — the usual default.
  • Shared singleton container: a base class holding a manually started static container reused across the whole module; fastest, needs data isolation discipline.
  • Per-test containers only when tests mutate global container state (topics, users).
  • Spring Boot 3.1+: @ServiceConnection on the container field replaces manual @DynamicPropertySource URL/credentials plumbing for supported types.

Framework test support side by side

Concern Spring Boot Quarkus Micronaut
Boot the app for a test @SpringBootTest @QuarkusTest @MicronautTest
Sliced/partial context @DataJpaTest, @WebMvcTest, @JsonTest — (context is cheap; profiles/@TestProfile) context is cheap; @Property overrides
HTTP assertions MockMvc (servlet), WebTestClient (reactive/real port) REST Assured (bundled convention) HttpClient injected against the embedded server
Replace a bean with a mock @MockitoBean (formerly @MockBean) @InjectMock @MockBean
Auto containers Testcontainers + @ServiceConnection Dev Services (implicit) Test Resources

All three run on JUnit 5 Jupiter extensions, so @Nested, @ParameterizedTest and custom extensions work identically inside framework tests.

Database state between tests

  • Transaction rollback: wrap each test in a transaction rolled back afterwards (Spring's default in @DataJpaTest). Fast, but hides commit-time behaviour (constraint deferral, triggers, REQUIRES_NEW), and useless for code that manages its own transactions.
  • Truncate between tests: slower but honest — the code commits for real. Truncate tables (or use a tool/extension) in @BeforeEach; combine with the singleton container.
  • Never rely on test ordering to share state; each test owns its fixtures.

Examples

@Testcontainers
@SpringBootTest
class OrderRepositoryIT {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:16-alpine");

    @Autowired OrderRepository orders;

    @Test
    void persistsAndReloadsOrder() {
        var saved = orders.save(Order.of("o-1", Money.eur(42)));
        assertThat(orders.findById(saved.id())).hasValueSatisfying(
            o -> assertThat(o.total()).isEqualTo(Money.eur(42)));
    }
}

Flyway migrations on the classpath run automatically on context start, so the container's schema matches production before the first test executes.

Related