In the age of AI-assisted development, writing code has never been faster.
But here is the uncomfortable truth that most teams discover only after production incidents:
AI generates implementations. It cannot decide what your tests should verify.
That responsibility - knowing what to test, with what fidelity, and why - belongs to engineers with platform knowledge and architectural judgment. Nowhere is this more visible than in integration testing.
This article is about a shift I see as non-negotiable for teams building reliable backend services in 2026.
The Deceptive Comfort of H2
For years, the default Spring Boot integration testing pattern looked like this.
Add H2 to the classpath, point your tests at an in-memory database, and tests run fast. No Docker. No external dependencies. Everything green.
The problem is that H2 is not your production database.
It does not enforce the same constraint semantics. It does not support all SQL constructs your migrations use. It does not run your schema through the same type coercion rules. And when your Flyway migrations grow in complexity - partial indexes, custom check constraints, advisory locks, partitioned tables - H2 quietly silences the errors that PostgreSQL would immediately surface.
You are not testing your system. You are testing a shadow of it.
I have seen services pass hundreds of H2-backed integration tests and then fail their first production deployment because a Flyway migration had a PostgreSQL-specific syntax that H2 accepted without complaint.
The Architectural Shift: Real Infrastructure, Every Run
The shift I advocate is straightforward in principle but meaningful in execution.
Instead of this:
Integration Test -> H2 In-Memory -> Schema (simplified)
Every integration test run connects to:
Related Articles
Shared topics and tags
Newsletter
Expert notes in your inbox
Subscribe for new articles.
Integration Test -> PostgreSQL Container (Testcontainers) -> Real Flyway Migrations -> Real Schema
Testcontainers starts a real PostgreSQL instance inside Docker for each test suite. Your Flyway migrations run exactly as they would in production. Hibernate validates the resulting schema. If any migration has a flaw, the test suite fails before a single test method executes.
This is not just better testing. It is a feedback loop that catches infrastructure regressions within seconds of committing code.
The Abstract Base Class Pattern
The architecture decision I consistently make in Spring Boot services is to centralize the Testcontainers setup in a single abstract base class that all integration tests extend.
One container. One Spring context. Shared across all test classes in the JVM.
The base class declares the container as public static final, meaning the PostgreSQL container starts once and is reused.
public static final PostgreSQLContainer<?> postgresContainer =
new PostgreSQLContainer<>("postgres:17")
.withDatabaseName("service_test")
.withUsername("testuser")
.withPassword("testpass")
.withReuse(true);
.withReuse(true) goes one step further - on a developer's local machine, Testcontainers will reuse the already-running container from a previous test run.
@DynamicPropertySource - The Right Hook for Dynamic Configuration
A critical implementation decision is where you start the container and how you wire its dynamic properties - JDBC URL, username, password - into the Spring Environment.
AI can generate integration test scaffolding quickly.
What it cannot decide:
database fidelity
container lifecycle
dependency hygiene
test architecture
Those remain engineering decisions.
Summary
Decision
Recommendation
Database
PostgreSQL via Testcontainers
Container lifecycle
@DynamicPropertySource
Container reuse
static container + .withReuse(true)
Test profile
dedicated test configuration
Web testing
MockMvc
Dependency management
include only needed dependencies
First test
context load + /actuator/health
Spring Boot 4
add spring-boot-starter-webmvc-test
Final Thoughts
AI accelerates implementation.
It does not replace architectural clarity.
Integration testing with real infrastructure is not ceremony - it is the foundation that gives teams confidence that what they build behaves correctly under real conditions.
Build that foundation correctly once. Everything on top of it benefits permanently.
If this was useful, share it with a teammate who is wrestling with flaky integration tests or the H2-versus-real-database debate.