diff --git a/data-jpa-testcontainers/README.md b/data-jpa-testcontainers/README.md new file mode 100644 index 0000000..861476d --- /dev/null +++ b/data-jpa-testcontainers/README.md @@ -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 `` 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. diff --git a/data-jpa-testcontainers/docs/output/00-full-test-run.txt b/data-jpa-testcontainers/docs/output/00-full-test-run.txt new file mode 100644 index 0000000..56ee610 --- /dev/null +++ b/data-jpa-testcontainers/docs/output/00-full-test-run.txt @@ -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] ------------------------------------------------------------------------ diff --git a/data-jpa-testcontainers/docs/output/01-postgresqlcontainer-relocation-javap.txt b/data-jpa-testcontainers/docs/output/01-postgresqlcontainer-relocation-javap.txt new file mode 100644 index 0000000..e46ce32 --- /dev/null +++ b/data-jpa-testcontainers/docs/output/01-postgresqlcontainer-relocation-javap.txt @@ -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 { + 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 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> extends org.testcontainers.containers.JdbcDatabaseContainer { + 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 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 diff --git a/data-jpa-testcontainers/docs/output/02-serviceconnection-unaffected-javap.txt b/data-jpa-testcontainers/docs/output/02-serviceconnection-unaffected-javap.txt new file mode 100644 index 0000000..6b89267 --- /dev/null +++ b/data-jpa-testcontainers/docs/output/02-serviceconnection-unaffected-javap.txt @@ -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.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>); + 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[] type(); +} diff --git a/data-jpa-testcontainers/docs/output/04-service-connection-without-guard-fails.txt b/data-jpa-testcontainers/docs/output/04-service-connection-without-guard-fails.txt new file mode 100644 index 0000000..4ee1462 --- /dev/null +++ b/data-jpa-testcontainers/docs/output/04-service-connection-without-guard-fails.txt @@ -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.(TestContextManager.java:142) + at org.springframework.test.context.TestContextManager.(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.(TestContextManager.java:142) + at org.springframework.test.context.TestContextManager.(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 diff --git a/data-jpa-testcontainers/docs/output/05-service-connection-skips-cleanly.txt b/data-jpa-testcontainers/docs/output/05-service-connection-skips-cleanly.txt new file mode 100644 index 0000000..4085a0f --- /dev/null +++ b/data-jpa-testcontainers/docs/output/05-service-connection-skips-cleanly.txt @@ -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] ------------------------------------------------------------------------ diff --git a/data-jpa-testcontainers/pom.xml b/data-jpa-testcontainers/pom.xml new file mode 100644 index 0000000..225e53c --- /dev/null +++ b/data-jpa-testcontainers/pom.xml @@ -0,0 +1,146 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 4.1.1 + + + + com.ankurm + data-jpa-testcontainers + 1.0.0 + data-jpa-testcontainers + @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. + jar + + + 25 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.flywaydb + flyway-core + + + + org.flywaydb + flyway-database-postgresql + + + + org.springframework.boot + spring-boot-starter-flyway + test + + + + org.postgresql + postgresql + runtime + + + com.h2database + h2 + test + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.boot + spring-boot-data-jpa-test + test + + + + org.springframework.boot + spring-boot-jpa-test + test + + + org.springframework.boot + spring-boot-jdbc-test + test + + + + + org.springframework.boot + spring-boot-testcontainers + test + + + + org.testcontainers + testcontainers-postgresql + test + + + + org.testcontainers + testcontainers-junit-jupiter + test + + + + + data-jpa-testcontainers + + + org.apache.maven.plugins + maven-surefire-plugin + + + + **/ServiceConnectionWithoutGuardFailsToStartTest.java + + + + + + diff --git a/data-jpa-testcontainers/src/main/java/com/ankurm/datajpa/DataJpaTestcontainersApplication.java b/data-jpa-testcontainers/src/main/java/com/ankurm/datajpa/DataJpaTestcontainersApplication.java new file mode 100644 index 0000000..ab32d3a --- /dev/null +++ b/data-jpa-testcontainers/src/main/java/com/ankurm/datajpa/DataJpaTestcontainersApplication.java @@ -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); + } +} diff --git a/data-jpa-testcontainers/src/main/java/com/ankurm/datajpa/Product.java b/data-jpa-testcontainers/src/main/java/com/ankurm/datajpa/Product.java new file mode 100644 index 0000000..739188e --- /dev/null +++ b/data-jpa-testcontainers/src/main/java/com/ankurm/datajpa/Product.java @@ -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; + } +} diff --git a/data-jpa-testcontainers/src/main/java/com/ankurm/datajpa/ProductRepository.java b/data-jpa-testcontainers/src/main/java/com/ankurm/datajpa/ProductRepository.java new file mode 100644 index 0000000..6c3f45f --- /dev/null +++ b/data-jpa-testcontainers/src/main/java/com/ankurm/datajpa/ProductRepository.java @@ -0,0 +1,10 @@ +package com.ankurm.datajpa; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface ProductRepository extends JpaRepository { + + Optional findBySku(String sku); +} diff --git a/data-jpa-testcontainers/src/main/resources/db/migration/V1__create_product.sql b/data-jpa-testcontainers/src/main/resources/db/migration/V1__create_product.sql new file mode 100644 index 0000000..1773ac6 --- /dev/null +++ b/data-jpa-testcontainers/src/main/resources/db/migration/V1__create_product.sql @@ -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 +); diff --git a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/DefaultReplacementTest.java b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/DefaultReplacementTest.java new file mode 100644 index 0000000..00ff4a9 --- /dev/null +++ b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/DefaultReplacementTest.java @@ -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(); + } +} diff --git a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayDoesNotRunUnderPlainDataJpaTestTest.java b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayDoesNotRunUnderPlainDataJpaTestTest.java new file mode 100644 index 0000000..2ac9d33 --- /dev/null +++ b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayDoesNotRunUnderPlainDataJpaTestTest.java @@ -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(); + } + } + } +} diff --git a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayRunsWhenExplicitlyImportedTest.java b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayRunsWhenExplicitlyImportedTest.java new file mode 100644 index 0000000..1620615 --- /dev/null +++ b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/FlywayRunsWhenExplicitlyImportedTest.java @@ -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(); + } + } +} diff --git a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/NoSubstitutionStillSilentlyUsesH2Test.java b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/NoSubstitutionStillSilentlyUsesH2Test.java new file mode 100644 index 0000000..de8b9a2 --- /dev/null +++ b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/NoSubstitutionStillSilentlyUsesH2Test.java @@ -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"); + } +} diff --git a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/ServiceConnectionSkipsCleanlyTest.java b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/ServiceConnectionSkipsCleanlyTest.java new file mode 100644 index 0000000..cda4877 --- /dev/null +++ b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/ServiceConnectionSkipsCleanlyTest.java @@ -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, + // this new org.testcontainers.postgresql.PostgreSQLContainer is not itself generic (confirmed + // with javap) -- it extends JdbcDatabaseContainer 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"); + } + } +} diff --git a/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/ServiceConnectionWithoutGuardFailsToStartTest.java b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/ServiceConnectionWithoutGuardFailsToStartTest.java new file mode 100644 index 0000000..86e49ba --- /dev/null +++ b/data-jpa-testcontainers/src/test/java/com/ankurm/datajpa/ServiceConnectionWithoutGuardFailsToStartTest.java @@ -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: + * + *
mvn test -Dtest=ServiceConnectionWithoutGuardFailsToStartTest
+ * + * 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. + } +}