From 59f6f241f3f92ea2fe373804b1f60e39f0bc661a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Wed, 1 Oct 2025 10:38:55 -0600 Subject: [PATCH] Add PostgreSQL container implementation under org.testcontainers.postgresql --- docs/modules/databases/postgres.md | 8 +- .../containers/PostgreSQLContainer.java | 3 + .../postgresql/PostgreSQLContainer.java | 144 ++++++++++++++++++ .../CustomizablePostgreSQLTest.java | 37 ----- .../CompatibleImageTest.java | 8 +- .../PostgreSQLContainerTest.java} | 21 ++- 6 files changed, 165 insertions(+), 56 deletions(-) create mode 100644 modules/postgresql/src/main/java/org/testcontainers/postgresql/PostgreSQLContainer.java delete mode 100644 modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/CustomizablePostgreSQLTest.java rename modules/postgresql/src/test/java/org/testcontainers/{containers => postgresql}/CompatibleImageTest.java (86%) rename modules/postgresql/src/test/java/org/testcontainers/{junit/postgresql/SimplePostgreSQLTest.java => postgresql/PostgreSQLContainerTest.java} (80%) diff --git a/docs/modules/databases/postgres.md b/docs/modules/databases/postgres.md index 043a01bb93e..bb2414fa2cc 100644 --- a/docs/modules/databases/postgres.md +++ b/docs/modules/databases/postgres.md @@ -7,7 +7,7 @@ Testcontainers module for [PostgresSQL](https://hub.docker.com/_/postgres) You can start a PostgreSQL container instance from any Java application by using: -[Container creation](../../../modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/SimplePostgreSQLTest.java) inside_block:container +[Container creation](../../../modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLContainerTest.java) inside_block:container See [Database containers](./index.md) for documentation and usage that is common to all relational database container types. @@ -28,19 +28,19 @@ See [JDBC](./jdbc.md) for documentation. * [pgvector/pgvector](https://hub.docker.com/r/pgvector/pgvector) -[Using pgvector](../../../modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java) inside_block:pgvectorContainer +[Using pgvector](../../../modules/postgresql/src/test/java/org/testcontainers/postgresql/CompatibleImageTest.java) inside_block:pgvectorContainer * [postgis/postgis](https://registry.hub.docker.com/r/postgis/postgis) -[Using PostGIS](../../../modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java) inside_block:postgisContainer +[Using PostGIS](../../../modules/postgresql/src/test/java/org/testcontainers/postgresql/CompatibleImageTest.java) inside_block:postgisContainer * [timescale/timescaledb](https://hub.docker.com/r/timescale/timescaledb) -[Using TimescaleDB](../../../modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java) inside_block:timescaledbContainer +[Using TimescaleDB](../../../modules/postgresql/src/test/java/org/testcontainers/postgresql/CompatibleImageTest.java) inside_block:timescaledbContainer ## Adding this module to your project dependencies diff --git a/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java b/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java index d96f30864d9..d45b9d5683d 100644 --- a/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java +++ b/modules/postgresql/src/main/java/org/testcontainers/containers/PostgreSQLContainer.java @@ -14,7 +14,10 @@ * Supported images: {@code postgres}, {@code pgvector/pgvector} *

* Exposed ports: 5432 + * + * @deprecated use {@link org.testcontainers.postgresql.PostgreSQLContainer} instead. */ +@Deprecated public class PostgreSQLContainer> extends JdbcDatabaseContainer { public static final String NAME = "postgresql"; diff --git a/modules/postgresql/src/main/java/org/testcontainers/postgresql/PostgreSQLContainer.java b/modules/postgresql/src/main/java/org/testcontainers/postgresql/PostgreSQLContainer.java new file mode 100644 index 00000000000..9b02019052d --- /dev/null +++ b/modules/postgresql/src/main/java/org/testcontainers/postgresql/PostgreSQLContainer.java @@ -0,0 +1,144 @@ +package org.testcontainers.postgresql; + +import org.jetbrains.annotations.NotNull; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.Set; + +/** + * Testcontainers implementation for PostgreSQL. + *

+ * Supported images: {@code postgres}, {@code pgvector/pgvector} + *

+ * Exposed ports: 5432 + */ +public class PostgreSQLContainer extends JdbcDatabaseContainer { + + public static final String NAME = "postgresql"; + + public static final String IMAGE = "postgres"; + + public static final String DEFAULT_TAG = "9.6.12"; + + private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("postgres"); + + private static final DockerImageName PGVECTOR_IMAGE_NAME = DockerImageName.parse("pgvector/pgvector"); + + public static final Integer POSTGRESQL_PORT = 5432; + + static final String DEFAULT_USER = "test"; + + static final String DEFAULT_PASSWORD = "test"; + + private String databaseName = "test"; + + private String username = "test"; + + private String password = "test"; + + private static final String FSYNC_OFF_OPTION = "fsync=off"; + + public PostgreSQLContainer(final String dockerImageName) { + this(DockerImageName.parse(dockerImageName)); + } + + public PostgreSQLContainer(final DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME, PGVECTOR_IMAGE_NAME); + + this.waitStrategy = + new LogMessageWaitStrategy() + .withRegEx(".*database system is ready to accept connections.*\\s") + .withTimes(2) + .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS)); + this.setCommand("postgres", "-c", FSYNC_OFF_OPTION); + + addExposedPort(POSTGRESQL_PORT); + } + + /** + * @return the ports on which to check if the container is ready + * @deprecated use {@link #getLivenessCheckPortNumbers()} instead + */ + @NotNull + @Override + @Deprecated + protected Set getLivenessCheckPorts() { + return super.getLivenessCheckPorts(); + } + + @Override + protected void configure() { + // Disable Postgres driver use of java.util.logging to reduce noise at startup time + withUrlParam("loggerLevel", "OFF"); + addEnv("POSTGRES_DB", databaseName); + addEnv("POSTGRES_USER", username); + addEnv("POSTGRES_PASSWORD", password); + } + + @Override + public String getDriverClassName() { + return "org.postgresql.Driver"; + } + + @Override + public String getJdbcUrl() { + String additionalUrlParams = constructUrlParameters("?", "&"); + return ( + "jdbc:postgresql://" + + getHost() + + ":" + + getMappedPort(POSTGRESQL_PORT) + + "/" + + databaseName + + additionalUrlParams + ); + } + + @Override + public String getDatabaseName() { + return databaseName; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getTestQueryString() { + return "SELECT 1"; + } + + @Override + public PostgreSQLContainer withDatabaseName(final String databaseName) { + this.databaseName = databaseName; + return self(); + } + + @Override + public PostgreSQLContainer withUsername(final String username) { + this.username = username; + return self(); + } + + @Override + public PostgreSQLContainer withPassword(final String password) { + this.password = password; + return self(); + } + + @Override + protected void waitUntilContainerStarted() { + getWaitStrategy().waitUntilReady(this); + } +} diff --git a/modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/CustomizablePostgreSQLTest.java b/modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/CustomizablePostgreSQLTest.java deleted file mode 100644 index fb09530913b..00000000000 --- a/modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/CustomizablePostgreSQLTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.testcontainers.junit.postgresql; - -import org.junit.jupiter.api.Test; -import org.testcontainers.PostgreSQLTestImages; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.db.AbstractContainerDatabaseTest; - -import java.sql.ResultSet; -import java.sql.SQLException; - -import static org.assertj.core.api.Assertions.assertThat; - -class CustomizablePostgreSQLTest extends AbstractContainerDatabaseTest { - - private static final String DB_NAME = "foo"; - - private static final String USER = "bar"; - - private static final String PWD = "baz"; - - @Test - void testSimple() throws SQLException { - try ( - PostgreSQLContainer postgres = new PostgreSQLContainer<>(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) - .withDatabaseName(DB_NAME) - .withUsername(USER) - .withPassword(PWD) - ) { - postgres.start(); - - ResultSet resultSet = performQuery(postgres, "SELECT 1"); - - int resultSetInt = resultSet.getInt(1); - assertThat(resultSetInt).as("A basic SELECT query succeeds").isEqualTo(1); - } - } -} diff --git a/modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java b/modules/postgresql/src/test/java/org/testcontainers/postgresql/CompatibleImageTest.java similarity index 86% rename from modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java rename to modules/postgresql/src/test/java/org/testcontainers/postgresql/CompatibleImageTest.java index 7101f4a9fb1..bdad4fb6a5c 100644 --- a/modules/postgresql/src/test/java/org/testcontainers/containers/CompatibleImageTest.java +++ b/modules/postgresql/src/test/java/org/testcontainers/postgresql/CompatibleImageTest.java @@ -1,4 +1,4 @@ -package org.testcontainers.containers; +package org.testcontainers.postgresql; import org.junit.jupiter.api.Test; import org.testcontainers.db.AbstractContainerDatabaseTest; @@ -15,7 +15,7 @@ class CompatibleImageTest extends AbstractContainerDatabaseTest { void pgvector() throws SQLException { try ( // pgvectorContainer { - PostgreSQLContainer pgvector = new PostgreSQLContainer<>("pgvector/pgvector:pg16") + PostgreSQLContainer pgvector = new PostgreSQLContainer("pgvector/pgvector:pg16") // } ) { pgvector.start(); @@ -30,7 +30,7 @@ void pgvector() throws SQLException { void postgis() throws SQLException { try ( // postgisContainer { - PostgreSQLContainer postgis = new PostgreSQLContainer<>( + PostgreSQLContainer postgis = new PostgreSQLContainer( DockerImageName.parse("postgis/postgis:16-3.4-alpine").asCompatibleSubstituteFor("postgres") ) // } @@ -47,7 +47,7 @@ void postgis() throws SQLException { void timescaledb() throws SQLException { try ( // timescaledbContainer { - PostgreSQLContainer timescaledb = new PostgreSQLContainer<>( + PostgreSQLContainer timescaledb = new PostgreSQLContainer( DockerImageName.parse("timescale/timescaledb:2.14.2-pg16").asCompatibleSubstituteFor("postgres") ) // } diff --git a/modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/SimplePostgreSQLTest.java b/modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLContainerTest.java similarity index 80% rename from modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/SimplePostgreSQLTest.java rename to modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLContainerTest.java index 7227f3bdd60..beefa3776b1 100644 --- a/modules/postgresql/src/test/java/org/testcontainers/junit/postgresql/SimplePostgreSQLTest.java +++ b/modules/postgresql/src/test/java/org/testcontainers/postgresql/PostgreSQLContainerTest.java @@ -1,8 +1,7 @@ -package org.testcontainers.junit.postgresql; +package org.testcontainers.postgresql; import org.junit.jupiter.api.Test; import org.testcontainers.PostgreSQLTestImages; -import org.testcontainers.containers.PostgreSQLContainer; import org.testcontainers.db.AbstractContainerDatabaseTest; import java.sql.ResultSet; @@ -13,7 +12,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; -class SimplePostgreSQLTest extends AbstractContainerDatabaseTest { +class PostgreSQLContainerTest extends AbstractContainerDatabaseTest { static { // Postgres JDBC driver uses JUL; disable it to avoid annoying, irrelevant, stderr logs during connection testing LogManager.getLogManager().getLogger("").setLevel(Level.OFF); @@ -22,7 +21,7 @@ class SimplePostgreSQLTest extends AbstractContainerDatabaseTest { @Test void testSimple() throws SQLException { try ( // container { - PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres:9.6.12") + PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:9.6.12") // } ) { postgres.start(); @@ -37,7 +36,7 @@ void testSimple() throws SQLException { @Test void testCommandOverride() throws SQLException { try ( - PostgreSQLContainer postgres = new PostgreSQLContainer<>(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) + PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) .withCommand("postgres -c max_connections=42") ) { postgres.start(); @@ -51,7 +50,7 @@ void testCommandOverride() throws SQLException { @Test void testUnsetCommand() throws SQLException { try ( - PostgreSQLContainer postgres = new PostgreSQLContainer<>(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) + PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) .withCommand("postgres -c max_connections=42") .withCommand() ) { @@ -66,7 +65,7 @@ void testUnsetCommand() throws SQLException { @Test void testMissingInitScript() { try ( - PostgreSQLContainer postgres = new PostgreSQLContainer<>(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) + PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) .withInitScript(null) ) { assertThatNoException().isThrownBy(postgres::start); @@ -76,7 +75,7 @@ void testMissingInitScript() { @Test void testExplicitInitScript() throws SQLException { try ( - PostgreSQLContainer postgres = new PostgreSQLContainer<>(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) + PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) .withInitScript("somepath/init_postgresql.sql") ) { postgres.start(); @@ -91,7 +90,7 @@ void testExplicitInitScript() throws SQLException { @Test void testExplicitInitScripts() throws SQLException { try ( - PostgreSQLContainer postgres = new PostgreSQLContainer<>(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) + PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) .withInitScripts("somepath/init_postgresql.sql", "somepath/init_postgresql_2.sql") ) { postgres.start(); @@ -112,7 +111,7 @@ void testExplicitInitScripts() throws SQLException { @Test void testWithAdditionalUrlParamInJdbcUrl() { try ( - PostgreSQLContainer postgres = new PostgreSQLContainer<>(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) + PostgreSQLContainer postgres = new PostgreSQLContainer(PostgreSQLTestImages.POSTGRES_TEST_IMAGE) .withUrlParam("charSet", "UNICODE") ) { postgres.start(); @@ -123,7 +122,7 @@ void testWithAdditionalUrlParamInJdbcUrl() { } } - private void assertHasCorrectExposedAndLivenessCheckPorts(PostgreSQLContainer postgres) { + private void assertHasCorrectExposedAndLivenessCheckPorts(PostgreSQLContainer postgres) { assertThat(postgres.getExposedPorts()).containsExactly(PostgreSQLContainer.POSTGRESQL_PORT); assertThat(postgres.getLivenessCheckPortNumbers()) .containsExactly(postgres.getMappedPort(PostgreSQLContainer.POSTGRESQL_PORT));