Skip to content

#29 - Use TestContainers for integration tests #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-r2dbc</artifactId>
<version>1.0.0.BUILD-SNAPSHOT</version>
<version>1.0.0.gh-29-SNAPSHOT</version>

<name>Spring Data R2DBC</name>
<description>Spring Data module for R2DBC.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

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
Expand All @@ -33,6 +35,8 @@
*/
public abstract class ExternalDatabase extends ExternalResource {

private static Logger LOG = LoggerFactory.getLogger(ExternalDatabase.class);

/**
* @return the post of the database service.
*/
Expand All @@ -53,16 +57,35 @@ public abstract class ExternalDatabase extends ExternalResource {
*/
public abstract String getUsername();

/**
* Throws an {@link AssumptionViolatedException} if the database cannot be reached.
*/
@Override
protected void before() {

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) {
throw new AssumptionViolatedException(
String.format("Cannot connect to %s:%d. Skipping tests.", getHostname(), getPort()));
LOG.debug("external database not available.", e);
}

return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@

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 final PostgreSQLContainer POSTGRESQL_CONTAINER = new PostgreSQLContainer();

public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
+ " id integer CONSTRAINT id PRIMARY KEY,\n" //
+ " name varchar(255) NOT NULL,\n" //
Expand All @@ -31,30 +35,60 @@ 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();

ExternalDatabase local = local();
if (local.checkValidity()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest inverting the condition. "If Docker is available, otherwise".
Some users might have an important state in their local database, or an outdated version of it, or many other reasons.

FYI you can check Docker's availability with:

return DockerClientFactory.instance().client() != null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand and somewhat agree with the concern.
My idea behind making it in this order was that one might want to use a specially configured database, e.g. for reproducing an issue that only appears with a special configuration.

I'm not at all sure if that is a relevant use case but we would lose it if we reverse the preference.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. What about something like Boolean.getProperty("spring.data.r2dbc.test.forceLocalDB")?

return local;
} else {
return testContainer();
}
}

/**
* 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() {

POSTGRESQL_CONTAINER.start();

return ProvidedDatabase.builder() //
.hostname("localhost") //
.port(POSTGRESQL_CONTAINER.getFirstMappedPort()) //
.database(POSTGRESQL_CONTAINER.getDatabaseName()) //
.username(POSTGRESQL_CONTAINER.getUsername()) //
.password(POSTGRESQL_CONTAINER.getPassword()).build();
}

/**
* 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());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

Expand Down