Integration Testing Strategies

Integration tests verify that different modules or services work together correctly. Unlike unit tests, they cross boundaries (Database, File System, Network) and are essential for catching "glue code" bugs.

Isolation with TestContainers

The modern standard for integration testing is TestContainers. It allows you to spin up lightweight, throwaway instances of your real infrastructure (PostgreSQL, Redis, Kafka) inside Docker containers during the test run.

Benefits:

Concrete Example: Postgres Integration

@Testcontainers
class UserRepositoryIT {

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

    @Test
    void shouldSaveAndRetrieveUser() {
        // Database is running in a real Docker container
        String jdbcUrl = postgres.getJdbcUrl();
        UserRepository repo = new UserRepository(jdbcUrl);

        repo.save(new User("bob", "bob@example.com"));
        Optional<User> found = repo.findByUsername("bob");

        assertTrue(found.isPresent());
        assertEquals("bob@example.com", found.get().getEmail());
    }
}

Mocking External APIs (WireMock)

When your system depends on a 3rd party REST API (e.g., Stripe, Twilio), do not hit the real production/sandbox servers. Use WireMock to spin up a local HTTP server that returns pre-defined JSON responses.

@Test
void shouldHandlePaymentFailure() {
    wireMockServer.stubFor(post(urlEqualTo("/v1/charges"))
        .willReturn(aResponse()
            .withStatus(402)
            .withBody("{\"error\": \"insufficient_funds\"}")));

    PaymentResult result = paymentClient.charge(100.0);
    assertEquals(PaymentStatus.FAILED, result.status());
}

Contract Testing (Pact)

In microservice architectures, integration tests often break because the "Provider" changed their API and the "Consumer" didn't know. Contract Testing formalizes the agreement:

  1. Consumer defines a "Pact" (expected request/response).
  2. Provider verifies their implementation against the Pact. This catches breaking changes before they hit production.

Database Strategy: Rollback vs. Truncate

Integration Testing Anti-Patterns