#29 - Use Testcontainers to run Postgres integration tests.
Postgres integration tests now run either with a locally installed and started database or if no such database can be found an instance gets started in a Docker container via Testcontainers. We prefer a database based on TestContainers. Only if this can't be obtained do we try to access a local database for Postgres. This minimizes the risk of accessing a database during tests that is not intended for that purpose. If the system property `spring.data.r2dbc.test.preferLocalDatabase` is set to "true" the local database is preferred. Original pull request: #51.
This commit is contained in:
committed by
Mark Paluch
parent
3a1085e244
commit
6f8bca6135
@@ -24,6 +24,8 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.AssumptionViolatedException;
|
||||
import org.junit.rules.ExternalResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* {@link ExternalResource} wrapper to encapsulate {@link ProvidedDatabase} and
|
||||
@@ -33,6 +35,8 @@ import org.junit.rules.ExternalResource;
|
||||
*/
|
||||
public abstract class ExternalDatabase extends ExternalResource {
|
||||
|
||||
private static Logger LOG = LoggerFactory.getLogger(ExternalDatabase.class);
|
||||
|
||||
/**
|
||||
* @return the post of the database service.
|
||||
*/
|
||||
@@ -53,18 +57,37 @@ public abstract class ExternalDatabase extends ExternalResource {
|
||||
*/
|
||||
public abstract String getUsername();
|
||||
|
||||
/**
|
||||
* Throws an {@link AssumptionViolatedException} if the database cannot be reached.
|
||||
*/
|
||||
@Override
|
||||
protected void before() {
|
||||
|
||||
try (Socket socket = new Socket()) {
|
||||
socket.connect(new InetSocketAddress(getHostname(), getPort()), Math.toIntExact(TimeUnit.SECONDS.toMillis(5)));
|
||||
|
||||
} catch (IOException e) {
|
||||
if (!checkValidity()) {
|
||||
throw new AssumptionViolatedException(
|
||||
String.format("Cannot connect to %s:%d. Skipping tests.", getHostname(), getPort()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* performs a test if the database can actually be reached.
|
||||
*
|
||||
* @return true, if the database could be reached.
|
||||
*/
|
||||
boolean checkValidity() {
|
||||
|
||||
try (Socket socket = new Socket()) {
|
||||
|
||||
socket.connect(new InetSocketAddress(getHostname(), getPort()), Math.toIntExact(TimeUnit.SECONDS.toMillis(5)));
|
||||
return true;
|
||||
|
||||
} catch (IOException e) {
|
||||
LOG.debug("external database not available.", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return password for the database user.
|
||||
*/
|
||||
@@ -122,4 +145,42 @@ public abstract class ExternalDatabase extends ExternalResource {
|
||||
return password;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link ExternalDatabase} that couldn't get constructed.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
static class NoSuchDatabase extends ExternalDatabase {
|
||||
|
||||
@Override
|
||||
boolean checkValidity() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHostname() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDatabase() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,24 @@ import io.r2dbc.postgresql.PostgresqlConnectionConfiguration;
|
||||
import io.r2dbc.postgresql.PostgresqlConnectionFactory;
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.postgresql.ds.PGSimpleDataSource;
|
||||
import org.springframework.data.r2dbc.testing.ExternalDatabase.ProvidedDatabase;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
|
||||
/**
|
||||
* Utility class for testing against Postgres.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class PostgresTestSupport {
|
||||
|
||||
private static ExternalDatabase testContainerDatabase;
|
||||
|
||||
public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
|
||||
+ " id integer CONSTRAINT id PRIMARY KEY,\n" //
|
||||
+ " name varchar(255) NOT NULL,\n" //
|
||||
@@ -31,30 +37,91 @@ public class PostgresTestSupport {
|
||||
public static String INSERT_INTO_LEGOSET = "INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)";
|
||||
|
||||
/**
|
||||
* Returns a locally provided database at {@code postgres:@localhost:5432/postgres}.
|
||||
* Returns a database either hosted locally at {@code postgres:@localhost:5432/postgres} or running inside Docker.
|
||||
*
|
||||
* @return
|
||||
* @return information about the database. Guaranteed to be not {@literal null}.
|
||||
*/
|
||||
public static ExternalDatabase database() {
|
||||
return local();
|
||||
|
||||
if (Boolean.getBoolean("spring.data.r2dbc.test.preferLocalDatabase")) {
|
||||
|
||||
return getFirstWorkingDatabase( //
|
||||
PostgresTestSupport::local, //
|
||||
PostgresTestSupport::testContainer //
|
||||
);
|
||||
} else {
|
||||
|
||||
return getFirstWorkingDatabase( //
|
||||
PostgresTestSupport::testContainer, //
|
||||
PostgresTestSupport::local //
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static ExternalDatabase getFirstWorkingDatabase(Supplier<ExternalDatabase> first,
|
||||
Supplier<ExternalDatabase> second) {
|
||||
|
||||
ExternalDatabase database = first.get();
|
||||
if (database.checkValidity()) {
|
||||
return database;
|
||||
} else {
|
||||
return second.get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a locally provided database at {@code postgres:@localhost:5432/postgres}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static ExternalDatabase local() {
|
||||
return ProvidedDatabase.builder().hostname("localhost").port(5432).database("postgres").username("postgres")
|
||||
|
||||
return ProvidedDatabase.builder() //
|
||||
.hostname("localhost") //
|
||||
.port(5432) //
|
||||
.database("postgres") //
|
||||
.username("postgres") //
|
||||
.password("").build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a database provided via Testcontainers.
|
||||
*/
|
||||
private static ExternalDatabase testContainer() {
|
||||
|
||||
if (testContainerDatabase == null) {
|
||||
|
||||
try {
|
||||
PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer();
|
||||
postgreSQLContainer.start();
|
||||
|
||||
testContainerDatabase = ProvidedDatabase.builder() //
|
||||
.hostname("localhost") //
|
||||
.port(postgreSQLContainer.getFirstMappedPort()) //
|
||||
.database(postgreSQLContainer.getDatabaseName()) //
|
||||
.username(postgreSQLContainer.getUsername()) //
|
||||
.password(postgreSQLContainer.getPassword()).build();
|
||||
|
||||
} catch (IllegalStateException ise) {
|
||||
// docker is not available.
|
||||
testContainerDatabase = new ExternalDatabase.NoSuchDatabase();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return testContainerDatabase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConnectionFactory} configured from the {@link ExternalDatabase}..
|
||||
*/
|
||||
public static ConnectionFactory createConnectionFactory(ExternalDatabase database) {
|
||||
return new PostgresqlConnectionFactory(PostgresqlConnectionConfiguration.builder().host(database.getHostname())
|
||||
.database(database.getDatabase()).username(database.getUsername()).password(database.getPassword()).build());
|
||||
|
||||
return new PostgresqlConnectionFactory(PostgresqlConnectionConfiguration.builder() //
|
||||
.host(database.getHostname()) //
|
||||
.database(database.getDatabase()) //
|
||||
.port(database.getPort()) //
|
||||
.username(database.getUsername()) //
|
||||
.password(database.getPassword()) //
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,4 +139,5 @@ public class PostgresTestSupport {
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,31 +33,35 @@ public class SqlServerTestSupport {
|
||||
|
||||
/**
|
||||
* Returns a locally provided database at {@code sqlserver:@localhost:1433/master}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static ExternalDatabase database() {
|
||||
return local();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a locally provided database at {@code postgres:@localhost:5432/postgres}.
|
||||
*
|
||||
* @return
|
||||
* Returns a locally provided database at {@code sqlserver:@localhost:1433/master}.
|
||||
*/
|
||||
private static ExternalDatabase local() {
|
||||
return ProvidedDatabase.builder().hostname("localhost").port(1433).database("master").username("sa")
|
||||
.password("A_Str0ng_Required_Password").build();
|
||||
|
||||
return ProvidedDatabase.builder() //
|
||||
.hostname("localhost") //
|
||||
.port(1433) //
|
||||
.database("master") //
|
||||
.username("sa") //
|
||||
.password("A_Str0ng_Required_Password") //
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ConnectionFactory} configured from the {@link ExternalDatabase}..
|
||||
*/
|
||||
public static ConnectionFactory createConnectionFactory(ExternalDatabase database) {
|
||||
|
||||
return new MssqlConnectionFactory(MssqlConnectionConfiguration.builder().host(database.getHostname()) //
|
||||
.database(database.getDatabase()) //
|
||||
.username(database.getUsername()) //
|
||||
.password(database.getPassword()) //
|
||||
.port(database.getPort()) //
|
||||
.build());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user