Skip to content

Commit 6f8bca6

Browse files
schaudermp911de
authored andcommitted
#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.
1 parent 3a1085e commit 6f8bca6

File tree

3 files changed

+150
-17
lines changed

3 files changed

+150
-17
lines changed

src/test/java/org/springframework/data/r2dbc/testing/ExternalDatabase.java

+63-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
import org.junit.AssumptionViolatedException;
2626
import org.junit.rules.ExternalResource;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
2729

2830
/**
2931
* {@link ExternalResource} wrapper to encapsulate {@link ProvidedDatabase} and
@@ -33,6 +35,8 @@
3335
*/
3436
public abstract class ExternalDatabase extends ExternalResource {
3537

38+
private static Logger LOG = LoggerFactory.getLogger(ExternalDatabase.class);
39+
3640
/**
3741
* @return the post of the database service.
3842
*/
@@ -53,16 +57,35 @@ public abstract class ExternalDatabase extends ExternalResource {
5357
*/
5458
public abstract String getUsername();
5559

60+
/**
61+
* Throws an {@link AssumptionViolatedException} if the database cannot be reached.
62+
*/
5663
@Override
5764
protected void before() {
5865

66+
if (!checkValidity()) {
67+
throw new AssumptionViolatedException(
68+
String.format("Cannot connect to %s:%d. Skipping tests.", getHostname(), getPort()));
69+
}
70+
}
71+
72+
/**
73+
* performs a test if the database can actually be reached.
74+
*
75+
* @return true, if the database could be reached.
76+
*/
77+
boolean checkValidity() {
78+
5979
try (Socket socket = new Socket()) {
80+
6081
socket.connect(new InetSocketAddress(getHostname(), getPort()), Math.toIntExact(TimeUnit.SECONDS.toMillis(5)));
82+
return true;
6183

6284
} catch (IOException e) {
63-
throw new AssumptionViolatedException(
64-
String.format("Cannot connect to %s:%d. Skipping tests.", getHostname(), getPort()));
85+
LOG.debug("external database not available.", e);
6586
}
87+
88+
return false;
6689
}
6790

6891
/**
@@ -122,4 +145,42 @@ public String getPassword() {
122145
return password;
123146
}
124147
}
148+
149+
/**
150+
* An {@link ExternalDatabase} that couldn't get constructed.
151+
*
152+
* @author Jens Schauder
153+
*/
154+
static class NoSuchDatabase extends ExternalDatabase {
155+
156+
@Override
157+
boolean checkValidity() {
158+
return false;
159+
}
160+
161+
@Override
162+
public int getPort() {
163+
throw new UnsupportedOperationException();
164+
}
165+
166+
@Override
167+
public String getHostname() {
168+
throw new UnsupportedOperationException();
169+
}
170+
171+
@Override
172+
public String getDatabase() {
173+
throw new UnsupportedOperationException();
174+
}
175+
176+
@Override
177+
public String getUsername() {
178+
throw new UnsupportedOperationException();
179+
}
180+
181+
@Override
182+
public String getPassword() {
183+
throw new UnsupportedOperationException();
184+
}
185+
}
125186
}

src/test/java/org/springframework/data/r2dbc/testing/PostgresTestSupport.java

+76-8
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,24 @@
44
import io.r2dbc.postgresql.PostgresqlConnectionFactory;
55
import io.r2dbc.spi.ConnectionFactory;
66

7+
import java.util.function.Supplier;
8+
79
import javax.sql.DataSource;
810

911
import org.postgresql.ds.PGSimpleDataSource;
1012
import org.springframework.data.r2dbc.testing.ExternalDatabase.ProvidedDatabase;
13+
import org.testcontainers.containers.PostgreSQLContainer;
1114

1215
/**
1316
* Utility class for testing against Postgres.
1417
*
1518
* @author Mark Paluch
19+
* @author Jens Schauder
1620
*/
1721
public class PostgresTestSupport {
1822

23+
private static ExternalDatabase testContainerDatabase;
24+
1925
public static String CREATE_TABLE_LEGOSET = "CREATE TABLE legoset (\n" //
2026
+ " id integer CONSTRAINT id PRIMARY KEY,\n" //
2127
+ " name varchar(255) NOT NULL,\n" //
@@ -31,30 +37,91 @@ public class PostgresTestSupport {
3137
public static String INSERT_INTO_LEGOSET = "INSERT INTO legoset (id, name, manual) VALUES($1, $2, $3)";
3238

3339
/**
34-
* Returns a locally provided database at {@code postgres:@localhost:5432/postgres}.
40+
* Returns a database either hosted locally at {@code postgres:@localhost:5432/postgres} or running inside Docker.
3541
*
36-
* @return
42+
* @return information about the database. Guaranteed to be not {@literal null}.
3743
*/
3844
public static ExternalDatabase database() {
39-
return local();
45+
46+
if (Boolean.getBoolean("spring.data.r2dbc.test.preferLocalDatabase")) {
47+
48+
return getFirstWorkingDatabase( //
49+
PostgresTestSupport::local, //
50+
PostgresTestSupport::testContainer //
51+
);
52+
} else {
53+
54+
return getFirstWorkingDatabase( //
55+
PostgresTestSupport::testContainer, //
56+
PostgresTestSupport::local //
57+
);
58+
}
59+
}
60+
61+
private static ExternalDatabase getFirstWorkingDatabase(Supplier<ExternalDatabase> first,
62+
Supplier<ExternalDatabase> second) {
63+
64+
ExternalDatabase database = first.get();
65+
if (database.checkValidity()) {
66+
return database;
67+
} else {
68+
return second.get();
69+
}
4070
}
4171

4272
/**
4373
* Returns a locally provided database at {@code postgres:@localhost:5432/postgres}.
44-
*
45-
* @return
4674
*/
4775
private static ExternalDatabase local() {
48-
return ProvidedDatabase.builder().hostname("localhost").port(5432).database("postgres").username("postgres")
76+
77+
return ProvidedDatabase.builder() //
78+
.hostname("localhost") //
79+
.port(5432) //
80+
.database("postgres") //
81+
.username("postgres") //
4982
.password("").build();
5083
}
5184

85+
/**
86+
* Returns a database provided via Testcontainers.
87+
*/
88+
private static ExternalDatabase testContainer() {
89+
90+
if (testContainerDatabase == null) {
91+
92+
try {
93+
PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer();
94+
postgreSQLContainer.start();
95+
96+
testContainerDatabase = ProvidedDatabase.builder() //
97+
.hostname("localhost") //
98+
.port(postgreSQLContainer.getFirstMappedPort()) //
99+
.database(postgreSQLContainer.getDatabaseName()) //
100+
.username(postgreSQLContainer.getUsername()) //
101+
.password(postgreSQLContainer.getPassword()).build();
102+
103+
} catch (IllegalStateException ise) {
104+
// docker is not available.
105+
testContainerDatabase = new ExternalDatabase.NoSuchDatabase();
106+
}
107+
108+
}
109+
110+
return testContainerDatabase;
111+
}
112+
52113
/**
53114
* Creates a new {@link ConnectionFactory} configured from the {@link ExternalDatabase}..
54115
*/
55116
public static ConnectionFactory createConnectionFactory(ExternalDatabase database) {
56-
return new PostgresqlConnectionFactory(PostgresqlConnectionConfiguration.builder().host(database.getHostname())
57-
.database(database.getDatabase()).username(database.getUsername()).password(database.getPassword()).build());
117+
118+
return new PostgresqlConnectionFactory(PostgresqlConnectionConfiguration.builder() //
119+
.host(database.getHostname()) //
120+
.database(database.getDatabase()) //
121+
.port(database.getPort()) //
122+
.username(database.getUsername()) //
123+
.password(database.getPassword()) //
124+
.build());
58125
}
59126

60127
/**
@@ -72,4 +139,5 @@ public static DataSource createDataSource(ExternalDatabase database) {
72139

73140
return dataSource;
74141
}
142+
75143
}

src/test/java/org/springframework/data/r2dbc/testing/SqlServerTestSupport.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -33,31 +33,35 @@ public class SqlServerTestSupport {
3333

3434
/**
3535
* Returns a locally provided database at {@code sqlserver:@localhost:1433/master}.
36-
*
37-
* @return
3836
*/
3937
public static ExternalDatabase database() {
4038
return local();
4139
}
4240

4341
/**
44-
* Returns a locally provided database at {@code postgres:@localhost:5432/postgres}.
45-
*
46-
* @return
42+
* Returns a locally provided database at {@code sqlserver:@localhost:1433/master}.
4743
*/
4844
private static ExternalDatabase local() {
49-
return ProvidedDatabase.builder().hostname("localhost").port(1433).database("master").username("sa")
50-
.password("A_Str0ng_Required_Password").build();
45+
46+
return ProvidedDatabase.builder() //
47+
.hostname("localhost") //
48+
.port(1433) //
49+
.database("master") //
50+
.username("sa") //
51+
.password("A_Str0ng_Required_Password") //
52+
.build();
5153
}
5254

5355
/**
5456
* Creates a new {@link ConnectionFactory} configured from the {@link ExternalDatabase}..
5557
*/
5658
public static ConnectionFactory createConnectionFactory(ExternalDatabase database) {
59+
5760
return new MssqlConnectionFactory(MssqlConnectionConfiguration.builder().host(database.getHostname()) //
5861
.database(database.getDatabase()) //
5962
.username(database.getUsername()) //
6063
.password(database.getPassword()) //
64+
.port(database.getPort()) //
6165
.build());
6266
}
6367

0 commit comments

Comments
 (0)