Add data-jpa-testcontainers module: @DataJpaTest's default substitution, the Flyway-never-runs trap, and @ServiceConnection on Testcontainers 2.0.5 (post #45)

This commit is contained in:
Claude
2026-10-08 11:52:08 +00:00
parent 5e4d481c02
commit 97f946136b
17 changed files with 1076 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
# data-jpa-testcontainers
Companion module for [`@DataJpaTest` with Testcontainers `@ServiceConnection` on Spring Boot 4.1](https://ankurm.com/) on ankurm.com.
## What this is
Six JUnit 5 test classes against one JPA repository (`Product`), each isolating one fact about
what `@DataJpaTest`'s default embedded-database substitution actually does, what
`@ServiceConnection` wires in its place, and what the defaults do not do (Flyway migrations are
not one of them). No `docs/NN-topic.md` chapters — everything beyond what fits in the post itself
is in the Javadoc on these six test classes and in the captured transcripts below.
**This module was built and tested in a sandbox with no Docker daemon available.** Five of the six
test classes run and pass (or cleanly skip) without one. The sixth demonstrates, on purpose, the
real failure you get from the one case that genuinely needs a container. See the versions table and
the test table below for exactly which is which.
## Versions
| Dependency | Version | Verified against |
|---|---|---|
| Spring Boot | 4.1.1 | `spring-boot-dependencies-4.1.1.pom` |
| Testcontainers | 2.0.5 | same BOM; `testcontainers-bom-2.0.5.pom` |
| Flyway | 12.4.0 | same BOM |
| Hibernate ORM | 7.4.5.Final | runtime log, this module's own test run |
| H2 | 2.4.240 | runtime log, this module's own test run |
| JDK | 25 (Temurin) | `java -version`, this module's own build |
## Quickstart
```bash
mvn test # 6 of 7 run; 1 skips cleanly
mvn test -Dtest=ServiceConnectionWithoutGuardFailsToStartTest # the one deliberate failure
```
## Test classes
| Class | What it proves | Needs Docker | Outcome here |
|---|---|---|---|
| `DefaultReplacementTest` | Plain `@DataJpaTest` substitutes an embedded H2 DataSource, and each test method is rolled back independently of execution order | No | Pass |
| `FlywayDoesNotRunUnderPlainDataJpaTestTest` | `@DataJpaTest`'s own auto-configuration import list never includes `FlywayAutoConfiguration` or `LiquibaseAutoConfiguration` — your migrations never run, substitution or not | No | Pass |
| `FlywayRunsWhenExplicitlyImportedTest` | The one-line fix: `@ImportAutoConfiguration(FlywayAutoConfiguration.class)` | No | Pass |
| `NoSubstitutionStillSilentlyUsesH2Test` | `@AutoConfigureTestDatabase(replace = Replace.NONE)` alone is not a guarantee of a real connection — with nothing else configured, ordinary `DataSourceAutoConfiguration` still silently hands you embedded H2 | No | Pass |
| `ServiceConnectionSkipsCleanlyTest` | The intended shape: `@ServiceConnection` + `@Container` + `replace = Replace.NONE`, guarded by Testcontainers 2.0's new `@EnabledIfDockerAvailable` | Yes (guarded) | Skipped |
| `ServiceConnectionWithoutGuardFailsToStartTest` | The same class, minus the guard | Yes (unguarded) | **Fails — on purpose.** Excluded from `mvn test` by name in `pom.xml`; run it explicitly |
## Captured output
| File | Contents |
|---|---|
| `docs/output/00-full-test-run.txt` | `mvn clean test` — 7 tests, 1 skip, build green |
| `docs/output/01-postgresqlcontainer-relocation-javap.txt` | `javap` proof that `org.testcontainers.postgresql.PostgreSQLContainer` (2.0.5's new home) and the deprecated `org.testcontainers.containers.PostgreSQLContainer` shim live in the *same* jar, and that the old class alone keeps the self-referential `<SELF extends ...>` generic |
| `docs/output/02-serviceconnection-unaffected-javap.txt` | `javap` proof that `JdbcContainerConnectionDetailsFactory` is typed against `org.testcontainers.containers.JdbcDatabaseContainer<?>` — unmoved by the 2.0 package split, which is why `@ServiceConnection` needed no changes |
| `docs/output/04-service-connection-without-guard-fails.txt` | The real `ContainerFetchException` / `IllegalStateException` from trying to start a Postgres container with no Docker daemon present, and no guard against that |
| `docs/output/05-service-connection-skips-cleanly.txt` | The same absence of Docker, this time behind `@EnabledIfDockerAvailable` — one line, `Skipped: 1`, no failure |
## What's sourced from code, not run here
Two things this article discusses are backed by reading Spring Boot's and Testcontainers' own
class files and import manifests rather than by a run in this sandbox, and are called out as such
in the post itself: what a *successful* real-Postgres run under `@ServiceConnection` would actually
return once a Docker daemon is present (the wiring is verified; the live round trip is not), and
Testcontainers' reusable-container feature (`.withReuse(true)` plus `~/.testcontainers.properties`),
which has nothing to do with Docker availability but was out of scope for a module built in one
sitting.
@@ -0,0 +1,184 @@
Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStoreType=JKS -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=34689 -Dhttp.nonProxyHosts=localhost|127.0.0.1|::1|127.*|0.*|::|169.254.*|api.anthropic.com|api-staging.anthropic.com|api-pr-preview.anthropic.com|mcp-proxy.anthropic.com|mcp-proxy-staging.anthropic.com|registry.npmjs.org|jsr.io|npm.jsr.io|pypi.org|files.pythonhosted.org|index.crates.io|proxy.golang.org|host.docker.internal|10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.20.*|172.21.*|172.22.*|172.23.*|172.24.*|172.25.*|172.26.*|172.27.*|172.28.*|172.29.*|172.30.*|172.31.*|192.168.*|100.64.0.0/10|*.svc.cluster.local|*.svc.cluster.local -Djdk.http.auth.tunneling.disabledSchemes= -Djdk.http.auth.proxying.disabledSchemes=
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/opt/maven/lib/guice-5.1.0-classes.jar)
WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner
WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< com.ankurm:data-jpa-testcontainers >-----------------
[INFO] Building data-jpa-testcontainers 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- clean:3.5.0:clean (default-clean) @ data-jpa-testcontainers ---
[INFO] Deleting /home/claude/spring-boot-demo/data-jpa-testcontainers/target
[INFO]
[INFO] --- resources:3.5.0:resources (default-resources) @ data-jpa-testcontainers ---
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.15.0:compile (default-compile) @ data-jpa-testcontainers ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 3 source files with javac [debug parameters release 25] to target/classes
[INFO]
[INFO] --- resources:3.5.0:testResources (default-testResources) @ data-jpa-testcontainers ---
[INFO] skip non existing resourceDirectory /home/claude/spring-boot-demo/data-jpa-testcontainers/src/test/resources
[INFO]
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ data-jpa-testcontainers ---
[INFO] Recompiling the module because of changed dependency.
[INFO] Compiling 6 source files with javac [debug parameters release 25] to target/test-classes
[INFO]
[INFO] --- surefire:3.5.6:test (default-test) @ data-jpa-testcontainers ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStoreType=JKS -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=34689 -Dhttp.nonProxyHosts=localhost|127.0.0.1|::1|127.*|0.*|::|169.254.*|api.anthropic.com|api-staging.anthropic.com|api-pr-preview.anthropic.com|mcp-proxy.anthropic.com|mcp-proxy-staging.anthropic.com|registry.npmjs.org|jsr.io|npm.jsr.io|pypi.org|files.pythonhosted.org|index.crates.io|proxy.golang.org|host.docker.internal|10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.20.*|172.21.*|172.22.*|172.23.*|172.24.*|172.25.*|172.26.*|172.27.*|172.28.*|172.29.*|172.30.*|172.31.*|192.168.*|100.64.0.0/10|*.svc.cluster.local|*.svc.cluster.local -Djdk.http.auth.tunneling.disabledSchemes= -Djdk.http.auth.proxying.disabledSchemes=
[INFO] Running com.ankurm.datajpa.DefaultReplacementTest
17:19:49.092 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.ankurm.datajpa.DefaultReplacementTest]: DefaultReplacementTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
17:19:49.365 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.DefaultReplacementTest
17:19:49.370 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.ankurm.datajpa.DefaultReplacementTest]: DefaultReplacementTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
17:19:49.405 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper -- Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.DefaultReplacementTest
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v4.1.1)
2026-10-08T17:19:50.032+05:30 INFO 2231 --- [ main] c.ankurm.datajpa.DefaultReplacementTest : Starting DefaultReplacementTest using Java 25.0.4.1 with PID 2231 (started by root in /home/claude/spring-boot-demo/data-jpa-testcontainers)
2026-10-08T17:19:50.034+05:30 INFO 2231 --- [ main] c.ankurm.datajpa.DefaultReplacementTest : No active profile set, falling back to 1 default profile: "default"
2026-10-08T17:19:50.614+05:30 INFO 2231 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2026-10-08T17:19:50.751+05:30 INFO 2231 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 126 ms. Found 1 JPA repository interface.
2026-10-08T17:19:50.849+05:30 INFO 2231 --- [ main] beddedDataSourceBeanFactoryPostProcessor : Replacing 'dataSource' DataSource bean with embedded version
2026-10-08T17:19:51.122+05:30 INFO 2231 --- [ main] o.s.j.d.e.EmbeddedDatabaseFactory : Starting embedded database: url='jdbc:h2:mem:27737b58-793d-4017-ae78-1a78ebfc5b27;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'
2026-10-08T17:19:51.815+05:30 INFO 2231 --- [ main] org.flywaydb.core.FlywayExecutor : Database: jdbc:h2:mem:27737b58-793d-4017-ae78-1a78ebfc5b27 (H2 2.4)
2026-10-08T17:19:51.888+05:30 WARN 2231 --- [ main] o.f.c.internal.database.base.Database : Using H2 2.4.240 which is newer than the version Flyway has been verified with. The latest verified version of H2 is 2.3.232.
2026-10-08T17:19:51.949+05:30 INFO 2231 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Schema history table "PUBLIC"."flyway_schema_history" does not exist yet
2026-10-08T17:19:51.952+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.040s)
2026-10-08T17:19:51.960+05:30 INFO 2231 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "PUBLIC"."flyway_schema_history" ...
2026-10-08T17:19:52.002+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "PUBLIC": << Empty Schema >>
2026-10-08T17:19:52.015+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "PUBLIC" to version "1 - create product"
2026-10-08T17:19:52.034+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC", now at version v1 (execution time 00:00.008s)
2026-10-08T17:19:52.268+05:30 INFO 2231 --- [ main] org.hibernate.orm.jpa : HHH008540: Processing PersistenceUnitInfo [name: default]
2026-10-08T17:19:52.332+05:30 INFO 2231 --- [ main] org.hibernate.orm.core : HHH000001: Hibernate ORM core version 7.4.5.Final
2026-10-08T17:19:52.886+05:30 INFO 2231 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2026-10-08T17:19:53.006+05:30 INFO 2231 --- [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info:
Database JDBC URL [jdbc:h2:mem:27737b58-793d-4017-ae78-1a78ebfc5b27]
Database driver: H2 JDBC Driver
Database dialect: H2Dialect
Database version: 2.4.240
Default catalog/schema: 27737B58-793D-4017-AE78-1A78EBFC5B27/PUBLIC
Autocommit mode: undefined/unknown
Isolation level: READ_COMMITTED [default READ_COMMITTED]
JDBC fetch size: 100
Pool: DataSourceConnectionProvider
Minimum pool size: undefined/unknown
Maximum pool size: undefined/unknown
2026-10-08T17:19:54.129+05:30 INFO 2231 --- [ main] org.hibernate.orm.core : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2026-10-08T17:19:54.145+05:30 INFO 2231 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2026-10-08T17:19:54.332+05:30 INFO 2231 --- [ main] o.s.d.j.r.query.QueryEnhancerFactories : Hibernate is in classpath; If applicable, HQL parser will be used.
2026-10-08T17:19:54.604+05:30 INFO 2231 --- [ main] c.ankurm.datajpa.DefaultReplacementTest : Started DefaultReplacementTest in 5.076 seconds (process running for 7.153)
Mockito is currently self-attaching to enable the inline-mock-maker. This will no longer work in future releases of the JDK. Please add Mockito as an agent to your build as described in Mockito's documentation: https://javadoc.io/doc/org.mockito/mockito-core/latest/org.mockito/org/mockito/Mockito.html#0.3
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
WARNING: A Java agent has been loaded dynamically (/root/.m2/repository/net/bytebuddy/byte-buddy-agent/1.18.11/byte-buddy-agent-1.18.11.jar)
WARNING: If a serviceability tool is in use, please run with -XX:+EnableDynamicAgentLoading to hide this warning
WARNING: If a serviceability tool is not in use, please run with -Djdk.instrument.traceUsage for more information
WARNING: Dynamic loading of agents will be disallowed by default in a future release
Hibernate: select count(*) from product p1_0
Hibernate: select p1_0.id,p1_0.name,p1_0.sku from product p1_0 where p1_0.sku=?
JDBC reports database product name: H2
Hibernate: insert into product (name,sku,id) values (?,?,default)
Hibernate: select p1_0.id,p1_0.name,p1_0.sku from product p1_0 where p1_0.sku=?
Hibernate: select count(*) from product p1_0
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7.329 s -- in com.ankurm.datajpa.DefaultReplacementTest
[INFO] Running com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test
2026-10-08T17:19:56.108+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test]: NoSubstitutionStillSilentlyUsesH2Test does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2026-10-08T17:19:56.189+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test
2026-10-08T17:19:56.197+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test]: NoSubstitutionStillSilentlyUsesH2Test does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2026-10-08T17:19:56.238+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v4.1.1)
2026-10-08T17:19:56.294+05:30 INFO 2231 --- [ main] .d.NoSubstitutionStillSilentlyUsesH2Test : Starting NoSubstitutionStillSilentlyUsesH2Test using Java 25.0.4.1 with PID 2231 (started by root in /home/claude/spring-boot-demo/data-jpa-testcontainers)
2026-10-08T17:19:56.295+05:30 INFO 2231 --- [ main] .d.NoSubstitutionStillSilentlyUsesH2Test : No active profile set, falling back to 1 default profile: "default"
2026-10-08T17:19:56.452+05:30 INFO 2231 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2026-10-08T17:19:56.469+05:30 INFO 2231 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 13 ms. Found 1 JPA repository interface.
2026-10-08T17:19:56.642+05:30 INFO 2231 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2026-10-08T17:19:56.665+05:30 INFO 2231 --- [ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Added connection conn9: url=jdbc:h2:mem:fb5a3fcb-6cc0-4a37-baff-7dbd0bfb51fd user=SA
2026-10-08T17:19:56.667+05:30 INFO 2231 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2026-10-08T17:19:56.673+05:30 INFO 2231 --- [ main] org.flywaydb.core.FlywayExecutor : Database: jdbc:h2:mem:fb5a3fcb-6cc0-4a37-baff-7dbd0bfb51fd (H2 2.4)
2026-10-08T17:19:56.686+05:30 WARN 2231 --- [ main] o.f.c.internal.database.base.Database : Using H2 2.4.240 which is newer than the version Flyway has been verified with. The latest verified version of H2 is 2.3.232.
2026-10-08T17:19:56.690+05:30 INFO 2231 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Schema history table "PUBLIC"."flyway_schema_history" does not exist yet
2026-10-08T17:19:56.691+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.002s)
2026-10-08T17:19:56.693+05:30 INFO 2231 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "PUBLIC"."flyway_schema_history" ...
2026-10-08T17:19:56.703+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "PUBLIC": << Empty Schema >>
2026-10-08T17:19:56.707+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "PUBLIC" to version "1 - create product"
2026-10-08T17:19:56.715+05:30 INFO 2231 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC", now at version v1 (execution time 00:00.004s)
2026-10-08T17:19:56.752+05:30 INFO 2231 --- [ main] org.hibernate.orm.jpa : HHH008540: Processing PersistenceUnitInfo [name: default]
2026-10-08T17:19:56.808+05:30 INFO 2231 --- [ main] o.s.o.j.p.SpringPersistenceUnitInfo : No LoadTimeWeaver setup: ignoring JPA class transformer
2026-10-08T17:19:56.814+05:30 INFO 2231 --- [ main] org.hibernate.orm.connections.pooling : HHH10001005: Database info:
Database JDBC URL [jdbc:h2:mem:fb5a3fcb-6cc0-4a37-baff-7dbd0bfb51fd]
Database driver: H2 JDBC Driver
Database dialect: H2Dialect
Database version: 2.4.240
Default catalog/schema: FB5A3FCB-6CC0-4A37-BAFF-7DBD0BFB51FD/PUBLIC
Autocommit mode: undefined/unknown
Isolation level: READ_COMMITTED [default READ_COMMITTED]
JDBC fetch size: 100
Pool: DataSourceConnectionProvider
Minimum pool size: undefined/unknown
Maximum pool size: undefined/unknown
2026-10-08T17:19:56.850+05:30 INFO 2231 --- [ main] org.hibernate.orm.core : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2026-10-08T17:19:56.851+05:30 INFO 2231 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2026-10-08T17:19:56.915+05:30 INFO 2231 --- [ main] .d.NoSubstitutionStillSilentlyUsesH2Test : Started NoSubstitutionStillSilentlyUsesH2Test in 0.668 seconds (process running for 9.464)
JDBC reports database product name: H2 (even with replace = Replace.NONE and no @ServiceConnection anywhere)
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.852 s -- in com.ankurm.datajpa.NoSubstitutionStillSilentlyUsesH2Test
[INFO] Running com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest
2026-10-08T17:19:56.952+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest]: FlywayDoesNotRunUnderPlainDataJpaTestTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2026-10-08T17:19:57.008+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest
2026-10-08T17:19:57.008+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest]: FlywayDoesNotRunUnderPlainDataJpaTestTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2026-10-08T17:19:57.064+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.137 s -- in com.ankurm.datajpa.FlywayDoesNotRunUnderPlainDataJpaTestTest
[INFO] Running com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest
2026-10-08T17:19:57.091+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest]: FlywayRunsWhenExplicitlyImportedTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2026-10-08T17:19:57.107+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest
2026-10-08T17:19:57.107+05:30 INFO 2231 --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest]: FlywayRunsWhenExplicitlyImportedTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2026-10-08T17:19:57.132+05:30 INFO 2231 --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.ankurm.datajpa.DataJpaTestcontainersApplication for test class com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest
flyway_schema_history row: version=1 description=create product success=true
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.085 s -- in com.ankurm.datajpa.FlywayRunsWhenExplicitlyImportedTest
2026-10-08T17:19:57.218+05:30 INFO 2231 --- [ main] org.testcontainers.DockerClientFactory : Testcontainers version: 2.0.5
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by com.sun.jna.Native in an unnamed module (file:/root/.m2/repository/net/java/dev/jna/jna/5.18.1/jna-5.18.1.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
2026-10-08T17:19:57.537+05:30 INFO 2231 --- [ main] .t.d.DockerMachineClientProviderStrategy : docker-machine executable was not found on PATH ([/opt/jdk-25/bin, /home/claude/.npm-global/bin, /nix/var/nix/profiles/default/bin, /root/.local/bin, /root/.cargo/bin, /usr/local/go/bin, /opt/node22/bin, /opt/maven/bin, /opt/gradle/bin, /opt/rbenv/bin, /root/.bun/bin, /usr/local/sbin, /usr/local/bin, /usr/sbin, /usr/bin, /sbin, /bin])
2026-10-08T17:19:57.538+05:30 ERROR 2231 --- [ main] o.t.d.DockerClientProviderStrategy : Could not find a valid Docker environment. Please check configuration. Attempted configurations were:
UnixSocketClientProviderStrategy: failed with exception InvalidConfigurationException (Could not find unix domain socket). Root cause NoSuchFileException (/var/run/docker.sock)
DockerDesktopClientProviderStrategy: failed with exception NullPointerException (Cannot invoke "java.nio.file.Path.toString()" because the return value of "org.testcontainers.dockerclient.DockerDesktopClientProviderStrategy.getSocketPath()" is null)As no valid configuration was found, execution cannot continue.
See https://java.testcontainers.org/on_failure.html for more details.
[INFO] Running com.ankurm.datajpa.ServiceConnectionSkipsCleanlyTest
[WARNING] Tests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.001 s -- in com.ankurm.datajpa.ServiceConnectionSkipsCleanlyTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 1
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 15.580 s
[INFO] Finished at: 2026-10-08T17:19:57+05:30
[INFO] ------------------------------------------------------------------------
@@ -0,0 +1,77 @@
$ javap -p org/testcontainers/postgresql/PostgreSQLContainer.class # testcontainers-postgresql-2.0.5.jar
Compiled from "PostgreSQLContainer.java"
public class org.testcontainers.postgresql.PostgreSQLContainer extends org.testcontainers.containers.JdbcDatabaseContainer<org.testcontainers.postgresql.PostgreSQLContainer> {
public static final java.lang.String NAME;
public static final java.lang.String IMAGE;
public static final java.lang.String DEFAULT_TAG;
private static final org.testcontainers.utility.DockerImageName DEFAULT_IMAGE_NAME;
private static final org.testcontainers.utility.DockerImageName PGVECTOR_IMAGE_NAME;
public static final java.lang.Integer POSTGRESQL_PORT;
static final java.lang.String DEFAULT_USER;
static final java.lang.String DEFAULT_PASSWORD;
private java.lang.String databaseName;
private java.lang.String username;
private java.lang.String password;
private static final java.lang.String FSYNC_OFF_OPTION;
public org.testcontainers.postgresql.PostgreSQLContainer(java.lang.String);
public org.testcontainers.postgresql.PostgreSQLContainer(org.testcontainers.utility.DockerImageName);
protected java.util.Set<java.lang.Integer> getLivenessCheckPorts();
protected void configure();
public java.lang.String getDriverClassName();
public java.lang.String getJdbcUrl();
public java.lang.String getDatabaseName();
public java.lang.String getUsername();
public java.lang.String getPassword();
public java.lang.String getTestQueryString();
public org.testcontainers.postgresql.PostgreSQLContainer withDatabaseName(java.lang.String);
public org.testcontainers.postgresql.PostgreSQLContainer withUsername(java.lang.String);
public org.testcontainers.postgresql.PostgreSQLContainer withPassword(java.lang.String);
protected void waitUntilContainerStarted();
public org.testcontainers.containers.JdbcDatabaseContainer withDatabaseName(java.lang.String);
public org.testcontainers.containers.JdbcDatabaseContainer withPassword(java.lang.String);
public org.testcontainers.containers.JdbcDatabaseContainer withUsername(java.lang.String);
static {};
}
$ javap -p org/testcontainers/containers/PostgreSQLContainer.class # SAME jar, deprecated shim
Compiled from "PostgreSQLContainer.java"
public class org.testcontainers.containers.PostgreSQLContainer<SELF extends org.testcontainers.containers.PostgreSQLContainer<SELF>> extends org.testcontainers.containers.JdbcDatabaseContainer<SELF> {
public static final java.lang.String NAME;
public static final java.lang.String IMAGE;
public static final java.lang.String DEFAULT_TAG;
private static final org.testcontainers.utility.DockerImageName DEFAULT_IMAGE_NAME;
private static final org.testcontainers.utility.DockerImageName PGVECTOR_IMAGE_NAME;
public static final java.lang.Integer POSTGRESQL_PORT;
static final java.lang.String DEFAULT_USER;
static final java.lang.String DEFAULT_PASSWORD;
private java.lang.String databaseName;
private java.lang.String username;
private java.lang.String password;
private static final java.lang.String FSYNC_OFF_OPTION;
public org.testcontainers.containers.PostgreSQLContainer();
public org.testcontainers.containers.PostgreSQLContainer(java.lang.String);
public org.testcontainers.containers.PostgreSQLContainer(org.testcontainers.utility.DockerImageName);
protected java.util.Set<java.lang.Integer> getLivenessCheckPorts();
protected void configure();
public java.lang.String getDriverClassName();
public java.lang.String getJdbcUrl();
public java.lang.String getDatabaseName();
public java.lang.String getUsername();
public java.lang.String getPassword();
public java.lang.String getTestQueryString();
public SELF withDatabaseName(java.lang.String);
public SELF withUsername(java.lang.String);
public SELF withPassword(java.lang.String);
protected void waitUntilContainerStarted();
public org.testcontainers.containers.JdbcDatabaseContainer withDatabaseName(java.lang.String);
public org.testcontainers.containers.JdbcDatabaseContainer withPassword(java.lang.String);
public org.testcontainers.containers.JdbcDatabaseContainer withUsername(java.lang.String);
static {};
}
$ javap -v org/testcontainers/containers/PostgreSQLContainer.class | grep -i deprecated
#7 = Utf8 Ljava/lang/Deprecated;
#232 = Utf8 Deprecated
Deprecated: true
java.lang.Deprecated
Deprecated: true
@@ -0,0 +1,15 @@
$ javap -p org/springframework/boot/jdbc/testcontainers/JdbcContainerConnectionDetailsFactory.class # spring-boot-jdbc-4.1.1.jar
Compiled from "JdbcContainerConnectionDetailsFactory.java"
class org.springframework.boot.jdbc.testcontainers.JdbcContainerConnectionDetailsFactory extends org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory<org.testcontainers.containers.JdbcDatabaseContainer<?>, org.springframework.boot.jdbc.autoconfigure.JdbcConnectionDetails> {
org.springframework.boot.jdbc.testcontainers.JdbcContainerConnectionDetailsFactory();
protected org.springframework.boot.jdbc.autoconfigure.JdbcConnectionDetails getContainerConnectionDetails(org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource<org.testcontainers.containers.JdbcDatabaseContainer<?>>);
protected org.springframework.boot.autoconfigure.service.connection.ConnectionDetails getContainerConnectionDetails(org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource);
}
$ javap -p org/springframework/boot/testcontainers/service/connection/ServiceConnection.class # spring-boot-testcontainers-4.1.1.jar
Compiled from "ServiceConnection.java"
public interface org.springframework.boot.testcontainers.service.connection.ServiceConnection extends java.lang.annotation.Annotation {
public abstract java.lang.String value();
public abstract java.lang.String name();
public abstract java.lang.Class<? extends org.springframework.boot.autoconfigure.service.connection.ConnectionDetails>[] type();
}
@@ -0,0 +1,151 @@
Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStoreType=JKS -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=34689 -Dhttp.nonProxyHosts=localhost|127.0.0.1|::1|127.*|0.*|::|169.254.*|api.anthropic.com|api-staging.anthropic.com|api-pr-preview.anthropic.com|mcp-proxy.anthropic.com|mcp-proxy-staging.anthropic.com|registry.npmjs.org|jsr.io|npm.jsr.io|pypi.org|files.pythonhosted.org|index.crates.io|proxy.golang.org|host.docker.internal|10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.20.*|172.21.*|172.22.*|172.23.*|172.24.*|172.25.*|172.26.*|172.27.*|172.28.*|172.29.*|172.30.*|172.31.*|192.168.*|100.64.0.0/10|*.svc.cluster.local|*.svc.cluster.local -Djdk.http.auth.tunneling.disabledSchemes= -Djdk.http.auth.proxying.disabledSchemes=
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/opt/maven/lib/guice-5.1.0-classes.jar)
WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner
WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< com.ankurm:data-jpa-testcontainers >-----------------
[INFO] Building data-jpa-testcontainers 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.5.0:resources (default-resources) @ data-jpa-testcontainers ---
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.15.0:compile (default-compile) @ data-jpa-testcontainers ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- resources:3.5.0:testResources (default-testResources) @ data-jpa-testcontainers ---
[INFO] skip non existing resourceDirectory /home/claude/spring-boot-demo/data-jpa-testcontainers/src/test/resources
[INFO]
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ data-jpa-testcontainers ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- surefire:3.5.6:test (default-test) @ data-jpa-testcontainers ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStoreType=JKS -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=34689 -Dhttp.nonProxyHosts=localhost|127.0.0.1|::1|127.*|0.*|::|169.254.*|api.anthropic.com|api-staging.anthropic.com|api-pr-preview.anthropic.com|mcp-proxy.anthropic.com|mcp-proxy-staging.anthropic.com|registry.npmjs.org|jsr.io|npm.jsr.io|pypi.org|files.pythonhosted.org|index.crates.io|proxy.golang.org|host.docker.internal|10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.20.*|172.21.*|172.22.*|172.23.*|172.24.*|172.25.*|172.26.*|172.27.*|172.28.*|172.29.*|172.30.*|172.31.*|192.168.*|100.64.0.0/10|*.svc.cluster.local|*.svc.cluster.local -Djdk.http.auth.tunneling.disabledSchemes= -Djdk.http.auth.proxying.disabledSchemes=
[INFO] Running com.ankurm.datajpa.ServiceConnectionWithoutGuardFailsToStartTest
17:20:10.044 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.ankurm.datajpa.ServiceConnectionWithoutGuardFailsToStartTest]: ServiceConnectionWithoutGuardFailsToStartTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
17:20:10.211 [main] INFO org.testcontainers.images.PullPolicy -- Image pull policy will be performed by: DefaultPullPolicy()
17:20:10.213 [main] INFO org.testcontainers.utility.ImageNameSubstitutor -- Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor')
17:20:10.253 [main] INFO org.testcontainers.DockerClientFactory -- Testcontainers version: 2.0.5
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by com.sun.jna.Native in an unnamed module (file:/root/.m2/repository/net/java/dev/jna/jna/5.18.1/jna-5.18.1.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
17:20:10.569 [main] INFO org.testcontainers.dockerclient.DockerMachineClientProviderStrategy -- docker-machine executable was not found on PATH ([/opt/jdk-25/bin, /home/claude/.npm-global/bin, /nix/var/nix/profiles/default/bin, /root/.local/bin, /root/.cargo/bin, /usr/local/go/bin, /opt/node22/bin, /opt/maven/bin, /opt/gradle/bin, /opt/rbenv/bin, /root/.bun/bin, /usr/local/sbin, /usr/local/bin, /usr/sbin, /usr/bin, /sbin, /bin])
17:20:10.570 [main] ERROR org.testcontainers.dockerclient.DockerClientProviderStrategy -- Could not find a valid Docker environment. Please check configuration. Attempted configurations were:
UnixSocketClientProviderStrategy: failed with exception InvalidConfigurationException (Could not find unix domain socket). Root cause NoSuchFileException (/var/run/docker.sock)
DockerDesktopClientProviderStrategy: failed with exception NullPointerException (Cannot invoke "java.nio.file.Path.toString()" because the return value of "org.testcontainers.dockerclient.DockerDesktopClientProviderStrategy.getSocketPath()" is null)As no valid configuration was found, execution cannot continue.
See https://java.testcontainers.org/on_failure.html for more details.
17:20:10.586 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils -- Could not detect default configuration classes for test class [com.ankurm.datajpa.ServiceConnectionWithoutGuardFailsToStartTest]: ServiceConnectionWithoutGuardFailsToStartTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
17:20:10.617 [main] INFO org.testcontainers.DockerClientFactory -- Testcontainers version: 2.0.5
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.877 s <<< FAILURE! -- in com.ankurm.datajpa.ServiceConnectionWithoutGuardFailsToStartTest
[ERROR] com.ankurm.datajpa.ServiceConnectionWithoutGuardFailsToStartTest -- Time elapsed: 0.877 s <<< ERROR!
org.testcontainers.containers.ContainerFetchException: Can't get Docker image: RemoteDockerImage(imageName=postgres:17, imagePullPolicy=DefaultPullPolicy(), imageNameSubstitutor=org.testcontainers.utility.ImageNameSubstitutor$LogWrappedImageNameSubstitutor@27f1bbe0)
at org.testcontainers.containers.GenericContainer.getDockerImageName(GenericContainer.java:1308)
at org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory.createSource(ServiceConnectionContextCustomizerFactory.java:90)
at org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory.lambda$collectSources$1(ServiceConnectionContextCustomizerFactory.java:65)
at org.springframework.core.annotation.TypeMappedAnnotations$AggregatesSpliterator.tryAdvance(TypeMappedAnnotations.java:591)
at org.springframework.core.annotation.TypeMappedAnnotations$AggregatesSpliterator.tryAdvance(TypeMappedAnnotations.java:558)
at java.base/java.util.Spliterator.forEachRemaining(Spliterator.java:332)
at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:803)
at org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory.lambda$collectSources$0(ServiceConnectionContextCustomizerFactory.java:65)
at org.springframework.util.ReflectionUtils.doWithLocalFields(ReflectionUtils.java:691)
at org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory.collectSources(ServiceConnectionContextCustomizerFactory.java:62)
at org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory.createContextCustomizer(ServiceConnectionContextCustomizerFactory.java:54)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.getContextCustomizers(AbstractTestContextBootstrapper.java:420)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:393)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildDefaultMergedContextConfiguration(AbstractTestContextBootstrapper.java:268)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:226)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildTestContext(AbstractTestContextBootstrapper.java:117)
at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.buildTestContext(SpringBootTestContextBootstrapper.java:102)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:142)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:126)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
at org.springframework.test.context.junit.jupiter.SpringExtension.getTestContextManager(SpringExtension.java:466)
at org.springframework.test.context.junit.jupiter.SpringExtension.beforeAll(SpringExtension.java:209)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1604)
Suppressed: org.testcontainers.containers.ContainerFetchException: Can't get Docker image: RemoteDockerImage(imageName=postgres:17, imagePullPolicy=DefaultPullPolicy(), imageNameSubstitutor=org.testcontainers.utility.ImageNameSubstitutor$LogWrappedImageNameSubstitutor@27f1bbe0)
at org.testcontainers.containers.GenericContainer.getDockerImageName(GenericContainer.java:1308)
at org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory.createSource(ServiceConnectionContextCustomizerFactory.java:90)
at org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory.lambda$collectSources$1(ServiceConnectionContextCustomizerFactory.java:65)
at org.springframework.core.annotation.TypeMappedAnnotations$AggregatesSpliterator.tryAdvance(TypeMappedAnnotations.java:591)
at org.springframework.core.annotation.TypeMappedAnnotations$AggregatesSpliterator.tryAdvance(TypeMappedAnnotations.java:558)
at java.base/java.util.Spliterator.forEachRemaining(Spliterator.java:332)
at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:803)
at org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory.lambda$collectSources$0(ServiceConnectionContextCustomizerFactory.java:65)
at org.springframework.util.ReflectionUtils.doWithLocalFields(ReflectionUtils.java:691)
at org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory.collectSources(ServiceConnectionContextCustomizerFactory.java:62)
at org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizerFactory.createContextCustomizer(ServiceConnectionContextCustomizerFactory.java:54)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.getContextCustomizers(AbstractTestContextBootstrapper.java:420)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:393)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildDefaultMergedContextConfiguration(AbstractTestContextBootstrapper.java:268)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:226)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildTestContext(AbstractTestContextBootstrapper.java:117)
at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.buildTestContext(SpringBootTestContextBootstrapper.java:102)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:142)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:126)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:328)
at org.springframework.test.context.junit.jupiter.SpringExtension.getTestContextManager(SpringExtension.java:466)
at org.springframework.test.context.junit.jupiter.SpringExtension.afterAll(SpringExtension.java:220)
... 1 more
Caused by: java.lang.IllegalStateException: Previous attempts to find a Docker environment failed. Will not retry. Please see logs and check configuration
at org.testcontainers.dockerclient.DockerClientProviderStrategy.getFirstValidStrategy(DockerClientProviderStrategy.java:229)
at org.testcontainers.DockerClientFactory.getOrInitializeStrategy(DockerClientFactory.java:154)
at org.testcontainers.DockerClientFactory.client(DockerClientFactory.java:196)
at org.testcontainers.images.LocalImagesCache.get(LocalImagesCache.java:31)
at org.testcontainers.images.AbstractImagePullPolicy.shouldPull(AbstractImagePullPolicy.java:18)
at org.testcontainers.images.RemoteDockerImage.resolve(RemoteDockerImage.java:79)
at org.testcontainers.images.RemoteDockerImage.resolve(RemoteDockerImage.java:35)
at org.testcontainers.utility.LazyFuture.getResolvedValue(LazyFuture.java:20)
at org.testcontainers.utility.LazyFuture.get(LazyFuture.java:41)
at org.testcontainers.containers.GenericContainer.getDockerImageName(GenericContainer.java:1306)
... 22 more
Caused by: java.lang.IllegalStateException: Could not find a valid Docker environment. Please see logs and check configuration
at org.testcontainers.dockerclient.DockerClientProviderStrategy.lambda$getFirstValidStrategy$7(DockerClientProviderStrategy.java:274)
at java.base/java.util.Optional.orElseThrow(Optional.java:403)
at org.testcontainers.dockerclient.DockerClientProviderStrategy.getFirstValidStrategy(DockerClientProviderStrategy.java:265)
at org.testcontainers.DockerClientFactory.getOrInitializeStrategy(DockerClientFactory.java:154)
at org.testcontainers.DockerClientFactory.client(DockerClientFactory.java:196)
at org.testcontainers.images.LocalImagesCache.get(LocalImagesCache.java:31)
at org.testcontainers.images.AbstractImagePullPolicy.shouldPull(AbstractImagePullPolicy.java:18)
at org.testcontainers.images.RemoteDockerImage.resolve(RemoteDockerImage.java:79)
at org.testcontainers.images.RemoteDockerImage.resolve(RemoteDockerImage.java:35)
at org.testcontainers.utility.LazyFuture.getResolvedValue(LazyFuture.java:20)
at org.testcontainers.utility.LazyFuture.get(LazyFuture.java:41)
at org.testcontainers.containers.GenericContainer.getDockerImageName(GenericContainer.java:1306)
... 22 more
[INFO]
[INFO] Results:
[INFO]
[ERROR] Errors:
[ERROR] ServiceConnectionWithoutGuardFailsToStartTest ? ContainerFetch Can't get Docker image: RemoteDockerImage(imageName=postgres:17, imagePullPolicy=DefaultPullPolicy(), imageNameSubstitutor=org.testcontainers.utility.ImageNameSubstitutor$LogWrappedImageNameSubstitutor@27f1bbe0)
[INFO]
[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.242 s
[INFO] Finished at: 2026-10-08T17:20:10+05:30
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.6:test (default-test) on project data-jpa-testcontainers:
[ERROR]
[ERROR] See /home/claude/spring-boot-demo/data-jpa-testcontainers/target/surefire-reports for the individual test results.
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
@@ -0,0 +1,56 @@
Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStoreType=JKS -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=34689 -Dhttp.nonProxyHosts=localhost|127.0.0.1|::1|127.*|0.*|::|169.254.*|api.anthropic.com|api-staging.anthropic.com|api-pr-preview.anthropic.com|mcp-proxy.anthropic.com|mcp-proxy-staging.anthropic.com|registry.npmjs.org|jsr.io|npm.jsr.io|pypi.org|files.pythonhosted.org|index.crates.io|proxy.golang.org|host.docker.internal|10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.20.*|172.21.*|172.22.*|172.23.*|172.24.*|172.25.*|172.26.*|172.27.*|172.28.*|172.29.*|172.30.*|172.31.*|192.168.*|100.64.0.0/10|*.svc.cluster.local|*.svc.cluster.local -Djdk.http.auth.tunneling.disabledSchemes= -Djdk.http.auth.proxying.disabledSchemes=
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::staticFieldBase has been called by com.google.inject.internal.aop.HiddenClassDefiner (file:/opt/maven/lib/guice-5.1.0-classes.jar)
WARNING: Please consider reporting this to the maintainers of class com.google.inject.internal.aop.HiddenClassDefiner
WARNING: sun.misc.Unsafe::staticFieldBase will be removed in a future release
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< com.ankurm:data-jpa-testcontainers >-----------------
[INFO] Building data-jpa-testcontainers 1.0.0
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.5.0:resources (default-resources) @ data-jpa-testcontainers ---
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO] Copying 1 resource from src/main/resources to target/classes
[INFO]
[INFO] --- compiler:3.15.0:compile (default-compile) @ data-jpa-testcontainers ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- resources:3.5.0:testResources (default-testResources) @ data-jpa-testcontainers ---
[INFO] skip non existing resourceDirectory /home/claude/spring-boot-demo/data-jpa-testcontainers/src/test/resources
[INFO]
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ data-jpa-testcontainers ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- surefire:3.5.6:test (default-test) @ data-jpa-testcontainers ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
Picked up JAVA_TOOL_OPTIONS: -Djavax.net.ssl.trustStore=/etc/ssl/certs/java/cacerts -Djavax.net.ssl.trustStoreType=JKS -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=34689 -Dhttp.nonProxyHosts=localhost|127.0.0.1|::1|127.*|0.*|::|169.254.*|api.anthropic.com|api-staging.anthropic.com|api-pr-preview.anthropic.com|mcp-proxy.anthropic.com|mcp-proxy-staging.anthropic.com|registry.npmjs.org|jsr.io|npm.jsr.io|pypi.org|files.pythonhosted.org|index.crates.io|proxy.golang.org|host.docker.internal|10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.20.*|172.21.*|172.22.*|172.23.*|172.24.*|172.25.*|172.26.*|172.27.*|172.28.*|172.29.*|172.30.*|172.31.*|192.168.*|100.64.0.0/10|*.svc.cluster.local|*.svc.cluster.local -Djdk.http.auth.tunneling.disabledSchemes= -Djdk.http.auth.proxying.disabledSchemes=
17:20:33.981 [main] INFO org.testcontainers.DockerClientFactory -- Testcontainers version: 2.0.5
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by com.sun.jna.Native in an unnamed module (file:/root/.m2/repository/net/java/dev/jna/jna/5.18.1/jna-5.18.1.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
17:20:34.343 [main] INFO org.testcontainers.dockerclient.DockerMachineClientProviderStrategy -- docker-machine executable was not found on PATH ([/opt/jdk-25/bin, /home/claude/.npm-global/bin, /nix/var/nix/profiles/default/bin, /root/.local/bin, /root/.cargo/bin, /usr/local/go/bin, /opt/node22/bin, /opt/maven/bin, /opt/gradle/bin, /opt/rbenv/bin, /root/.bun/bin, /usr/local/sbin, /usr/local/bin, /usr/sbin, /usr/bin, /sbin, /bin])
17:20:34.345 [main] ERROR org.testcontainers.dockerclient.DockerClientProviderStrategy -- Could not find a valid Docker environment. Please check configuration. Attempted configurations were:
UnixSocketClientProviderStrategy: failed with exception InvalidConfigurationException (Could not find unix domain socket). Root cause NoSuchFileException (/var/run/docker.sock)
DockerDesktopClientProviderStrategy: failed with exception NullPointerException (Cannot invoke "java.nio.file.Path.toString()" because the return value of "org.testcontainers.dockerclient.DockerDesktopClientProviderStrategy.getSocketPath()" is null)As no valid configuration was found, execution cannot continue.
See https://java.testcontainers.org/on_failure.html for more details.
[INFO] Running com.ankurm.datajpa.ServiceConnectionSkipsCleanlyTest
[WARNING] Tests run: 1, Failures: 0, Errors: 0, Skipped: 1, Time elapsed: 0.059 s -- in com.ankurm.datajpa.ServiceConnectionSkipsCleanlyTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 1
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.721 s
[INFO] Finished at: 2026-10-08T17:20:34+05:30
[INFO] ------------------------------------------------------------------------
+146
View File
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.1</version>
<relativePath/>
</parent>
<groupId>com.ankurm</groupId>
<artifactId>data-jpa-testcontainers</artifactId>
<version>1.0.0</version>
<name>data-jpa-testcontainers</name>
<description>@DataJpaTest with Testcontainers @ServiceConnection on Spring Boot 4.1.1 / Testcontainers 2.0.5:
what the default embedded-database substitution actually does, what @ServiceConnection wires in its
place, and what each one looks like when something is missing.</description>
<packaging>jar</packaging>
<properties>
<java.version>25</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<!-- Flyway 12 split its database-specific parsers out of flyway-core (confirmed in the
db-migrations-flyway-liquibase module's findings). This repo's migration is run twice in
this article: once for real against H2 (no extra dependency needed, H2 support is built
into flyway-core) and once, in the real-Postgres test classes, against whatever
PostgreSQLContainer provides — which needs this dialect module on the classpath even
though that test class cannot start a container in this sandbox. -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<!-- flyway-core on the classpath is NOT enough to get FlywayAutoConfiguration — that class
lives in its own starter, a further instance of Boot 4's per-technology module split
(confirmed by comparing against the db-migrations-flyway-liquibase module, which needs the
exact same starter). Test scope only: this article's main app never runs Flyway itself, the
two test classes that explicitly re-import FlywayAutoConfiguration are what need the class
on the classpath at all. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- @DataJpaTest itself: relocated out of spring-boot-test-autoconfigure in Boot 4.1 into its
own module. Confirmed by unzipping this jar: the only class in it is DataJpaTest plus its
bootstrapper and type-exclude filter — TestEntityManager and AutoConfigureTestDatabase are
NOT in here, they come in transitively from the two dependencies below. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-data-jpa-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Pulled in transitively by spring-boot-data-jpa-test above (spring-boot-jpa-test for
TestEntityManager, spring-boot-jdbc-test for AutoConfigureTestDatabase/TestDatabaseAutoConfiguration)
— listed explicitly here only so the module table in the README has something to point at;
removing these two lines changes nothing on the classpath. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-jpa-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-jdbc-test</artifactId>
<scope>test</scope>
</dependency>
<!-- @ServiceConnection itself — unaffected by the Testcontainers 2.0 package moves below,
because it is typed against org.testcontainers.containers.JdbcDatabaseContainer, which did
not move. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
<!-- Testcontainers 2.0.5. PostgreSQLContainer lives at org.testcontainers.postgresql in this
version; org.testcontainers.containers.PostgreSQLContainer still exists as a working
@Deprecated subclass, in this SAME jar (confirmed with `unzip -l`: both classes are inside
testcontainers-postgresql-2.0.5.jar, not split across two artifacts). -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-postgresql</artifactId>
<scope>test</scope>
</dependency>
<!-- The JUnit 5 extension's artifact was renamed from junit-jupiter to
testcontainers-junit-jupiter in 2.0; its package, org.testcontainers.junit.jupiter, did not
change. This version also ships a new @EnabledIfDockerAvailable condition, used below. -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>data-jpa-testcontainers</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- This class exists to demonstrate a real startup FAILURE on purpose: a container with
no guard against a missing Docker daemon. It is run individually with
`mvn test -Dtest=ServiceConnectionWithoutGuardFailsToStartTest` to capture that
failure for docs/output — excluded here so the module's default `mvn test` build
stays green. -->
<excludes>
<exclude>**/ServiceConnectionWithoutGuardFailsToStartTest.java</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,12 @@
package com.ankurm.datajpa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DataJpaTestcontainersApplication {
public static void main(String[] args) {
SpringApplication.run(DataJpaTestcontainersApplication.class, args);
}
}
@@ -0,0 +1,49 @@
package com.ankurm.datajpa;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
/**
* A deliberately boring entity. The point of this module is not the domain model -- it is what
* backs the DataSource underneath it, which is why every field here is the simplest type that
* will do the job.
*
* See the "@DataJpaTest with Testcontainers @ServiceConnection" article on ankurm.com for the
* chapter this entity supports.
*/
@Entity
@Table(name = "product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String sku;
private String name;
protected Product() {
// JPA
}
public Product(String sku, String name) {
this.sku = sku;
this.name = name;
}
public Long getId() {
return id;
}
public String getSku() {
return sku;
}
public String getName() {
return name;
}
}
@@ -0,0 +1,10 @@
package com.ankurm.datajpa;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface ProductRepository extends JpaRepository<Product, Long> {
Optional<Product> findBySku(String sku);
}
@@ -0,0 +1,5 @@
create table product (
id bigint generated by default as identity primary key,
sku varchar(64) not null unique,
name varchar(255) not null
);
@@ -0,0 +1,58 @@
package com.ankurm.datajpa;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jpa.test.autoconfigure.TestEntityManager;
import javax.sql.DataSource;
import java.sql.Connection;
import static org.assertj.core.api.Assertions.assertThat;
/**
* No @ServiceConnection, no Testcontainers import anywhere in this file. Plain @DataJpaTest,
* exactly as most tutorials show it.
*
* What it actually runs against, and why the two tests below pass or fail in isolation from each
* other, is the subject of the article's second and third sections.
*/
@DataJpaTest
class DefaultReplacementTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private ProductRepository productRepository;
@Autowired
private DataSource dataSource;
@Test
void whateverIsBehindThisDataSourceIsNotPostgres() throws Exception {
String productName;
try (Connection connection = dataSource.getConnection()) {
productName = connection.getMetaData().getDatabaseProductName();
}
System.out.println("JDBC reports database product name: " + productName);
assertThat(productName).isNotEqualTo("PostgreSQL");
}
@Test
void insertedRowIsVisibleWithinThisTest() {
entityManager.persistAndFlush(new Product("SKU-1", "Widget"));
assertThat(productRepository.findBySku("SKU-1")).isPresent();
assertThat(productRepository.count()).isEqualTo(1);
}
@Test
void previousTestsRowIsGoneInThisOne() {
// If @DataJpaTest did not roll each test back, this would see the row the previous test
// method inserted. It does not -- each test method gets its own transaction, rolled back
// when the method returns, regardless of test execution order.
assertThat(productRepository.count()).isZero();
assertThat(productRepository.findBySku("SKU-1")).isEmpty();
}
}
@@ -0,0 +1,49 @@
package com.ankurm.datajpa;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import static org.assertj.core.api.Assertions.assertThat;
/**
* This module has a real Flyway migration, {@code V1__create_product.sql}, and flyway-core is a
* main-scope dependency. A plain @DataJpaTest never runs it anyway -- not because of the
* embedded-database substitution, but because @DataJpaTest's own curated set of auto-configuration
* imports simply does not include FlywayAutoConfiguration or LiquibaseAutoConfiguration at all.
* Confirmed by reading the actual import manifests Boot ships inside spring-boot-jdbc-test.jar and
* spring-boot-data-jpa-test.jar -- neither file mentions Flyway or Liquibase, in any form, under
* any replace setting.
*
* The {@code product} table exists in this test for a completely different reason: Hibernate's own
* {@code ddl-auto}, defaulted on because Boot never detects a schema-management tool in this
* slice's context. See {@link FlywayRunsWhenExplicitlyImportedTest} for the one-line fix.
*/
@DataJpaTest
class FlywayDoesNotRunUnderPlainDataJpaTestTest {
@Autowired
private DataSource dataSource;
@Test
void productTableExistsButFlywayNeverRan() throws Exception {
try (Connection connection = dataSource.getConnection()) {
try (ResultSet flywayTable = connection.getMetaData()
.getTables(null, null, "FLYWAY_SCHEMA_HISTORY", null)) {
assertThat(flywayTable.next())
.as("flyway_schema_history should not exist -- Flyway never ran")
.isFalse();
}
try (ResultSet productTable = connection.getMetaData()
.getTables(null, null, "PRODUCT", null)) {
assertThat(productTable.next())
.as("product table should exist anyway -- Hibernate's ddl-auto made it")
.isTrue();
}
}
}
}
@@ -0,0 +1,52 @@
package com.ankurm.datajpa;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.flyway.autoconfigure.FlywayAutoConfiguration;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import static org.assertj.core.api.Assertions.assertThat;
/**
* The one-line fix for {@link FlywayDoesNotRunUnderPlainDataJpaTestTest}: pull
* FlywayAutoConfiguration into the slice yourself. Once it is in the context, Boot's own
* schema-management detection sees it, flips Hibernate's ddl-auto back to {@code none}, and the
* real migration runs against the same substituted (here, H2) DataSource.
*/
@DataJpaTest
@ImportAutoConfiguration(FlywayAutoConfiguration.class)
class FlywayRunsWhenExplicitlyImportedTest {
@Autowired
private DataSource dataSource;
@Test
void flywayHistoryTableProvesTheMigrationRanThisTime() throws Exception {
// Flyway's own history table always carries a bookkeeping row at installed_rank = -1
// ("<< Flyway Schema History table created >>") ahead of the real migration rows --
// easy to miss if a query only reads the first row back, which is what an earlier draft
// of this test did. The real V1 migration is installed_rank = 1.
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery(
"select \"installed_rank\", \"version\", \"description\", \"success\" "
+ "from \"flyway_schema_history\" where \"installed_rank\" = 1")) {
assertThat(rs.next()).isTrue();
String version = rs.getString(2);
String description = rs.getString(3);
boolean success = rs.getBoolean(4);
System.out.println("flyway_schema_history row: version=" + version
+ " description=" + description + " success=" + success);
assertThat(version).isEqualTo("1");
assertThat(description).isEqualTo("create product");
assertThat(success).isTrue();
assertThat(rs.next()).isFalse();
}
}
}
@@ -0,0 +1,51 @@
package com.ankurm.datajpa;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import javax.sql.DataSource;
import java.sql.Connection;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase.Replace;
/**
* The actually-surprising half of this trap. The intuition is that
* {@code @AutoConfigureTestDatabase(replace = Replace.NONE)} turns off the embedded substitution,
* so without a real connection configured anywhere this test should fail to start -- that was this
* class's original name and premise, until running it against this sandbox's lack of Docker proved
* that assumption wrong.
*
* What {@code replace = Replace.NONE} actually turns off is narrower: only
* {@code TestDatabaseAutoConfiguration}'s own bean-post-processing step, the one that replaces an
* already-configured production DataSource bean. It does nothing to the ordinary
* {@code DataSourceAutoConfiguration}, which auto-provisions an embedded database whenever no
* {@code spring.datasource.url} is set and an embedded driver is on the classpath -- completely
* independent machinery, with no awareness of the test slice at all. There is no
* {@code @ServiceConnection} in this class, and no connection properties anywhere. Replace.NONE is
* set. It still silently gets H2, with no warning that anything was substituted, because nothing
* Replace.NONE knows about was. The real log line is in
* {@code docs/output/00-full-test-run.txt}: a HikariDataSource backed by {@code jdbc:h2:mem:...},
* never the "Replacing 'dataSource' DataSource bean with embedded version" line that
* {@link DefaultReplacementTest} produces.
*/
@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class NoSubstitutionStillSilentlyUsesH2Test {
@Autowired
private DataSource dataSource;
@Test
void replaceNoneIsNotTheSameAsARealConnection() throws Exception {
String productName;
try (Connection connection = dataSource.getConnection()) {
productName = connection.getMetaData().getDatabaseProductName();
}
System.out.println("JDBC reports database product name: " + productName
+ " (even with replace = Replace.NONE and no @ServiceConnection anywhere)");
assertThat(productName).isEqualTo("H2");
}
}
@@ -0,0 +1,57 @@
package com.ankurm.datajpa;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.EnabledIfDockerAvailable;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.postgresql.PostgreSQLContainer;
import javax.sql.DataSource;
import java.sql.Connection;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase.Replace;
/**
* This is the article's intended, finished shape: a real Postgres container wired in as the
* DataSource for a @DataJpaTest slice, with the default embedded substitution turned off so the
* real connection is actually used.
*
* {@code @EnabledIfDockerAvailable} is new in Testcontainers 2.0's JUnit 5 extension (confirmed by
* `unzip -l` against the 2.0.5 artifact; it does not exist in 1.x). Without it, this class would
* try to start the container and fail loudly the moment Docker is not reachable -- see
* {@link ServiceConnectionWithoutGuardFailsToStartTest} for exactly what that looks like. With it,
* JUnit reports the whole class as cleanly SKIPPED instead, which is what actually happened the one
* time this class ran for this article: no Docker daemon in the build sandbox, captured in
* {@code docs/output/05-service-connection-skips-cleanly.txt}.
*/
@DataJpaTest
@Testcontainers
@AutoConfigureTestDatabase(replace = Replace.NONE)
@EnabledIfDockerAvailable
class ServiceConnectionSkipsCleanlyTest {
// Unlike the deprecated org.testcontainers.containers.PostgreSQLContainer<SELF extends ...>,
// this new org.testcontainers.postgresql.PostgreSQLContainer is not itself generic (confirmed
// with javap) -- it extends JdbcDatabaseContainer<PostgreSQLContainer> directly. Code ported
// from the old import that wrote `PostgreSQLContainer<?>` needs that type parameter dropped.
@Container
@ServiceConnection
static PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:17");
@Autowired
private DataSource dataSource;
@Test
void realDatabaseProductNameIsPostgres() throws Exception {
try (Connection connection = dataSource.getConnection()) {
String productName = connection.getMetaData().getDatabaseProductName();
System.out.println("JDBC reports database product name: " + productName);
assertThat(productName).isEqualTo("PostgreSQL");
}
}
}
@@ -0,0 +1,39 @@
package com.ankurm.datajpa;
import org.junit.jupiter.api.Test;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.postgresql.PostgreSQLContainer;
import static org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase.Replace;
/**
* Identical to {@link ServiceConnectionSkipsCleanlyTest} except for one missing annotation:
* no {@code @EnabledIfDockerAvailable}. NOT part of this module's default `mvn test` run -- see
* the surefire exclude in pom.xml. Run it on its own with:
*
* <pre>mvn test -Dtest=ServiceConnectionWithoutGuardFailsToStartTest</pre>
*
* Without the guard, the Testcontainers JUnit extension tries to start the container regardless of
* what @AutoConfigureTestDatabase says, and that start call is what actually fails. The captured
* failure -- including whichever of Spring Boot's own
* {@code DockerEnvironmentNotFoundFailureAnalyzer} or the raw Testcontainers exception came through
* -- is in {@code docs/output/04-service-connection-without-guard-fails.txt}.
*/
@DataJpaTest
@Testcontainers
@AutoConfigureTestDatabase(replace = Replace.NONE)
class ServiceConnectionWithoutGuardFailsToStartTest {
@Container
@ServiceConnection
static PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:17");
@Test
void neverReached() {
// The test body is irrelevant -- container startup fails before this runs.
}
}