By default @DataJpaTest replaces your DataSource with an embedded DB. How do you test against the real (production-like) database instead?
answer
- @AutoConfigureTestDatabase replace attribute
- Default = Replace.ANY → embedded
- Replace.NONE keeps real DataSource
- Pair with Testcontainers / @ServiceConnection
- H2-passes-Postgres-fails fidelity gap
basics
~10 sAdd @AutoConfigureTestDatabase(replace = Replace.NONE) to keep your configured DataSource, and point the test at a real database — commonly a Postgres/MySQL container via Testcontainers instead of the default in-memory H2.
solid answer
~40 s@DataJpaTest bundles @AutoConfigureTestDatabase, whose default is Replace.ANY — it swaps whatever DataSource you configured for an embedded in-memory one (H2/HSQLDB/Derby). To run against a real database you override that with @AutoConfigureTestDatabase(replace = Replace.NONE), which tells Spring Boot to keep the DataSource from your configuration/profile. In practice you pair this with Testcontainers so the test spins up a disposable Postgres/MySQL matching production. This gives fidelity for vendor-specific SQL, native queries, and DB-enforced constraints that H2 emulates imperfectly. The trade-off is speed and infrastructure: containers are slower and need Docker. Many teams standardize on Replace.NONE + Testcontainers precisely because 'passes on H2, fails on Postgres' is a classic production bug source.
code
java · 18 lines@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Testcontainers
class OrderRepositoryIT {
@Container
@ServiceConnection // Boot wires JDBC url/user/password automatically
static PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:16");
@Autowired
private OrderRepository orders;
@Test
void nativeUpsertWorksOnPostgres() {
orders.upsert(new Order(1L, "NEW"));
assertThat(orders.findById(1L)).isPresent();
}
}go deeper
Know the default is an embedded DB and that you can turn replacement off.
Name the replace = Replace.NONE override and the three Replace enum values, and pair it with Testcontainers.
Explain the fidelity trade-off and give concrete H2-vs-Postgres divergences that justify a real DB.
Make the org-level call: standardize repository tests on Testcontainers to eliminate dialect drift while keeping context slices for speed.
## The default behavior `@DataJpaTest` is meta-annotated with `@AutoConfigureTestDatabase`. That annotation has a `replace` attribute whose default value is **`Replace.ANY`**, meaning: *replace any auto-configured or manually-configured `DataSource` with an embedded in-memory one*. Spring Boot looks for an embedded driver on the classpath (H2, then HSQLDB, then Derby) and builds an in-memory DB. That's why a plain `@DataJpaTest` ignores your `application.yml` datasource and uses H2. ## Overriding it Add the annotation explicitly with a different `replace` value: ```java @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class UserRepositoryIT { ... } ``` `Replace.NONE` keeps whatever `DataSource` your configuration provides. The `Replace` enum values: - **`ANY`** (default) — replace any DataSource (embedded or real) with an embedded one. - **`AUTO_CONFIGURED`** — replace only a DataSource that Spring Boot auto-configured (leave an explicitly-defined one alone). - **`NONE`** — never replace; use the real configured DataSource. ## Providing a real database With `Replace.NONE` you must actually give the test a database. Two common approaches: 1. **Testcontainers** — spin up a real Postgres/MySQL in Docker. Modern Spring Boot supports `@ServiceConnection` on a `@Container` field (or `@DynamicPropertySource`) to wire the JDBC URL/credentials automatically. 2. **A shared external DB / profile** — point a test profile at a real instance (less isolated, discouraged for unit-level tests). ```java @DataJpaTest @AutoConfigureTestDatabase(replace = Replace.NONE) @Testcontainers class UserRepositoryIT { @Container @ServiceConnection static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16"); @Autowired UserRepository repo; } ``` ## Why bother — fidelity H2 emulates other dialects imperfectly. Things that pass on H2 but break on Postgres/MySQL: - Native/vendor-specific SQL (`@Query(nativeQuery = true)`). - JSON/array/enum column types, `ON CONFLICT`, window functions. - Case-sensitivity, sequence vs identity generation, constraint naming. - Liquibase/Flyway migrations that use vendor DDL. ## Trade-offs - **Embedded (default)**: fast, zero infra, but lower fidelity. - **Replace.NONE + Testcontainers**: high fidelity, catches dialect bugs, but slower startup and requires Docker in CI. ## Gotchas - Using `Replace.NONE` **without** providing a DataSource → context fails to start. - Leaving H2 on the classpath is fine, but `Replace.ANY` (the default) will still hijack the DataSource unless you override — a common 'why is my test not hitting Postgres?' surprise. - With migrations (Liquibase/Flyway), run them against the container so schema matches production.
- What is the default value of the replace attribute on @AutoConfigureTestDatabase?Replace.ANY — it replaces any DataSource (even an explicitly configured one) with an embedded in-memory database. You must set Replace.NONE to keep the real one.
- Why not just always test on H2 since it's faster?H2 emulates other dialects imperfectly. Native SQL, JSON/array types, sequence generation, and vendor DDL can pass on H2 yet fail on production Postgres/MySQL, so H2-only tests give false confidence.
- How does Testcontainers get its JDBC URL into the Spring context?Via @ServiceConnection on the @Container field (Boot 3.1+), or the older @DynamicPropertySource that registers spring.datasource.* from the running container.
saying these in an interview costs you the question
- Thinking @DataJpaTest hits production DB by default
- Believing switching to @SpringBootTest alone changes the DataSource replacement
- Using Replace.NONE without actually providing a DataSource
- Assuming H2 is a faithful stand-in for Postgres/MySQL