@SpringBootTest and Testcontainers

Full Integration Test

@SpringBootTest starts the whole application. With RANDOM_PORT it even starts Tomcat, and you call it over real HTTP:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
class ProductApiIT {

    @Autowired TestRestTemplate rest;

    @Test
    void createThenFetch() {
        var req = new CreateProductRequest("ELE-2001", "Laptop Stand", "Aluminium",
                new BigDecimal("1499.00"), 40, "Electronics");

        ResponseEntity<ProductResponse> created = rest.postForEntity("/api/products", req, ProductResponse.class);
        assertThat(created.getStatusCode()).isEqualTo(HttpStatus.CREATED);

        ProductResponse fetched = rest.getForObject(created.getHeaders().getLocation(), ProductResponse.class);
        assertThat(fetched.name()).isEqualTo("Laptop Stand");
    }
}

Testcontainers: a Real Database in Tests

H2 behaves differently from MySQL/PostgreSQL in small ways. Testcontainers starts a real database in Docker for the test run. Since Boot 3.1, @ServiceConnection wires its URL, user and password into Spring automatically.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
</dependency>
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class ProductApiPostgresIT {

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

    @Autowired TestRestTemplate rest;

    @Test
    void listWorksOnRealPostgres() {
        ResponseEntity<String> res = rest.getForEntity("/api/products", String.class);
        assertThat(res.getStatusCode().is2xxSuccessful()).isTrue();
    }
}

Example

./mvnw verify

Example output

INFO  tc.postgres:16-alpine : Creating container for image: postgres:16-alpine
INFO  tc.postgres:16-alpine : Container postgres:16-alpine started in PT2.1S
INFO  c.s.ProductApiPostgresIT : Started ProductApiPostgresIT in 5.733 seconds
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

Note: Testcontainers needs Docker running on the machine (or CI runner). GitHub Actions Ubuntu runners have Docker installed.