Skip to content

Commit 51c7664

Browse files
committed
#420 - Introducing infrastructure to find and detect required databases.
Tests using the new Rule can try multiple strategies to find a suitable database. Typically first looking for a local one and alternatively creating one using Docker via Testcontainers. If no database is found the tests get skipped by default. If the tests are run with `-DignoreMissingInfrastructure=false` the first test with each Rule instance will fail, making the build fail. Added the InfrastructureRule to ignore/fail tests depending on the presence of a required infrastructure. Implemented that Rule for R2DBC and Solr.
1 parent 5d536b1 commit 51c7664

File tree

19 files changed

+570
-137
lines changed

19 files changed

+570
-137
lines changed

pom.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2-
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
33
<modelVersion>4.0.0</modelVersion>
44

55
<groupId>org.springframework.data.examples</groupId>
@@ -16,6 +16,7 @@
1616
</parent>
1717

1818
<modules>
19+
<module>util</module>
1920
<module>bom</module>
2021
<module>couchbase</module>
2122
<module>elasticsearch</module>
@@ -36,6 +37,7 @@
3637
<properties>
3738
<apt.version>1.1.3</apt.version>
3839
<lombok.version>1.18.0</lombok.version>
40+
<testcontainers.version>1.9.1</testcontainers.version>
3941
</properties>
4042

4143
<profiles>

r2dbc/example/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,11 @@
1313

1414
<name>Spring Data R2DBC - Example</name>
1515

16+
<dependencies>
17+
<dependency>
18+
<groupId>org.springframework.data.examples</groupId>
19+
<artifactId>spring-data-examples-utils</artifactId>
20+
<version>${project.version}</version>
21+
</dependency>
22+
</dependencies>
1623
</project>

r2dbc/example/src/test/java/example/springdata/r2dbc/basics/CustomerRepositoryIntegrationTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package example.springdata.r2dbc.basics;
1717

18+
import example.springdata.test.util.InfrastructureRule;
1819
import reactor.core.publisher.Hooks;
1920
import reactor.test.StepVerifier;
2021

@@ -23,6 +24,7 @@
2324
import java.util.List;
2425

2526
import org.junit.Before;
27+
import org.junit.Rule;
2628
import org.junit.Test;
2729
import org.junit.runner.RunWith;
2830
import org.springframework.beans.factory.annotation.Autowired;
@@ -31,7 +33,10 @@
3133
import org.springframework.test.context.junit4.SpringRunner;
3234

3335
/**
36+
* Tests demonstrating the use of R2DBC.
37+
*
3438
* @author Oliver Gierke
39+
* @author Jens Schauder
3540
*/
3641
@RunWith(SpringRunner.class)
3742
@SpringBootTest(classes = InfrastructureConfiguration.class)
@@ -40,6 +45,8 @@ public class CustomerRepositoryIntegrationTests {
4045
@Autowired CustomerRepository customers;
4146
@Autowired DatabaseClient database;
4247

48+
@Rule @Autowired public InfrastructureRule requiresPostgres;
49+
4350
@Before
4451
public void setUp() {
4552

r2dbc/example/src/test/java/example/springdata/r2dbc/basics/InfrastructureConfiguration.java

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,34 @@
1515
*/
1616
package example.springdata.r2dbc.basics;
1717

18+
import example.springdata.test.util.InfrastructureRule;
1819
import io.r2dbc.postgresql.PostgresqlConnectionConfiguration;
1920
import io.r2dbc.postgresql.PostgresqlConnectionFactory;
2021
import io.r2dbc.spi.ConnectionFactory;
2122

23+
import org.jetbrains.annotations.NotNull;
2224
import org.springframework.context.annotation.Bean;
2325
import org.springframework.context.annotation.Configuration;
2426
import org.springframework.data.r2dbc.function.DatabaseClient;
2527
import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory;
2628
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
2729
import org.testcontainers.containers.PostgreSQLContainer;
28-
29-
import javax.annotation.PreDestroy;
30+
import reactor.test.StepVerifier;
3031

3132
/**
3233
* @author Oliver Gierke
3334
*/
3435
@Configuration
3536
class InfrastructureConfiguration {
3637

37-
private PostgreSQLContainer postgres = new PostgreSQLContainer();
38+
@Bean
39+
InfrastructureRule<PostgresqlConnectionConfiguration> infrastructureRule() {
40+
41+
return new InfrastructureRule<>( //
42+
this::checkForlLocalPostgres, //
43+
this::startPostgresInDocker //
44+
);
45+
}
3846

3947
@Bean
4048
CustomerRepository customerRepository(R2dbcRepositoryFactory factory) {
@@ -61,22 +69,50 @@ DatabaseClient databaseClient(ConnectionFactory factory) {
6169
@Bean
6270
PostgresqlConnectionFactory connectionFactory() {
6371

72+
return new PostgresqlConnectionFactory(infrastructureRule().getInfo());
73+
}
74+
75+
@NotNull
76+
private InfrastructureRule.InfrastructureInfo<PostgresqlConnectionConfiguration> startPostgresInDocker() {
6477

78+
PostgreSQLContainer postgres = new PostgreSQLContainer();
6579
postgres.start();
6680

67-
PostgresqlConnectionConfiguration config = PostgresqlConnectionConfiguration.builder() //
81+
PostgresqlConnectionConfiguration configuration = PostgresqlConnectionConfiguration.builder() //
6882
.host(postgres.getContainerIpAddress()) //
6983
.port(postgres.getFirstMappedPort()) //
7084
.database(postgres.getDatabaseName()) //
7185
.username(postgres.getUsername()) //
7286
.password(postgres.getPassword()) //
7387
.build();
7488

75-
return new PostgresqlConnectionFactory(config);
89+
return new InfrastructureRule.InfrastructureInfo<>(true, configuration, null, postgres::stop);
7690
}
7791

78-
@PreDestroy
79-
void shutdown() {
80-
postgres.stop();
92+
@NotNull
93+
private InfrastructureRule.InfrastructureInfo<PostgresqlConnectionConfiguration> checkForlLocalPostgres() {
94+
95+
PostgresqlConnectionConfiguration configuration = PostgresqlConnectionConfiguration.builder() //
96+
.host("localhost") //
97+
.port(5432) //
98+
.database("postgres") //
99+
.username("postgres") //
100+
.password("") //
101+
.build();
102+
103+
try {
104+
105+
new PostgresqlConnectionFactory(configuration).create()
106+
.as(StepVerifier::create) //
107+
.assertNext(c -> {
108+
}) //
109+
.verifyComplete();
110+
} catch (AssertionError re) {
111+
112+
return new InfrastructureRule.InfrastructureInfo<>(false, null, re, () ->{});
113+
}
114+
115+
return new InfrastructureRule.InfrastructureInfo<>(true, configuration, null, () ->{});
81116
}
117+
82118
}

solr/example/pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@
1818
<version>${project.version}</version>
1919
<scope>test</scope>
2020
</dependency>
21+
<dependency>
22+
<groupId>org.springframework.data.examples</groupId>
23+
<artifactId>spring-data-examples-utils</artifactId>
24+
<version>${project.version}</version>
25+
</dependency>
26+
<dependency>
27+
<groupId>org.testcontainers</groupId>
28+
<artifactId>testcontainers</artifactId>
29+
<version>${testcontainers.version}</version>
30+
<scope>test</scope>
31+
</dependency>
2132
</dependencies>
2233

2334
</project>

solr/example/src/main/java/example/springdata/solr/product/Product.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public class Product {
4343
private @Indexed(name = "cat") List<String> category;
4444
private @Indexed(name = "store") Point location;
4545
private @Indexed String description;
46-
private @Indexed boolean inStock;
46+
private @Indexed Boolean inStock;
4747
private @Indexed Integer popularity;
4848
private @Score Float score;
4949
}

solr/example/src/test/java/example/springdata/solr/AdvancedSolrRepositoryTests.java

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,20 @@
2222

2323
import example.springdata.solr.product.Product;
2424
import example.springdata.solr.product.ProductRepository;
25-
import example.springdata.solr.test.util.RequiresSolrServer;
25+
import example.springdata.test.util.InfrastructureRule;
2626

2727
import java.time.Duration;
2828
import java.util.Arrays;
2929
import java.util.Optional;
3030

31-
import org.junit.ClassRule;
31+
import org.junit.After;
32+
import org.junit.Before;
33+
import org.junit.Rule;
3234
import org.junit.Test;
3335
import org.junit.runner.RunWith;
3436
import org.springframework.beans.factory.annotation.Autowired;
3537
import org.springframework.boot.test.context.SpringBootTest;
36-
import org.springframework.context.annotation.Configuration;
3738
import org.springframework.data.domain.PageRequest;
38-
import org.springframework.data.repository.CrudRepository;
3939
import org.springframework.data.solr.core.SolrOperations;
4040
import org.springframework.data.solr.core.query.Function;
4141
import org.springframework.data.solr.core.query.Query;
@@ -48,33 +48,41 @@
4848
* @author Christoph Strobl
4949
* @author Oliver Gierke
5050
* @author Mark Paluch
51+
* @author Jens Schauder
5152
*/
5253
@RunWith(SpringRunner.class)
5354
@SpringBootTest
5455
public class AdvancedSolrRepositoryTests {
5556

56-
public static @ClassRule RequiresSolrServer requiresRunningServer = RequiresSolrServer.onLocalhost();
57+
@Rule @Autowired public InfrastructureRule requiresRunningServer;
5758

58-
@Configuration
59-
static class Config extends SolrTestConfiguration {
60-
61-
@Override
62-
protected void doInitTestData(CrudRepository<Product, String> repository) {
59+
@Autowired ProductRepository repository;
60+
@Autowired SolrOperations operations;
6361

64-
Product playstation = Product.builder().id("id-1").name("Playstation")
65-
.description("The Sony playstation was the top selling gaming system in 1994.").popularity(5).build();
66-
Product playstation2 = Product.builder().id("id-2").name("Playstation Two")
67-
.description("Playstation two is the successor of playstation in 2000.").build();
68-
Product superNES = Product.builder().id("id-3").name("Super Nintendo").popularity(3).build();
69-
Product nintendo64 = Product.builder().id("id-4").name("N64").description("Nintendo 64").popularity(2).build();
7062

71-
repository.saveAll(Arrays.asList(playstation, playstation2, superNES, nintendo64));
72-
}
63+
/**
64+
* Remove test data when context is shut down.
65+
*/
66+
public @After void deleteDocumentsOnShutdown() {
67+
repository.deleteAll();
7368
}
7469

75-
@Autowired ProductRepository repository;
76-
@Autowired SolrOperations operations;
70+
/**
71+
* Initialize Solr instance with test data once context has started.
72+
*/
73+
public @Before void initWithTestData() throws InterruptedException {
7774

75+
repository.deleteAll();
76+
77+
Product playstation = Product.builder().id("id-1").name("Playstation")
78+
.description("The Sony playstation was the top selling gaming system in 1994.").popularity(5).build();
79+
Product playstation2 = Product.builder().id("id-2").name("Playstation Two")
80+
.description("Playstation two is the successor of playstation in 2000.").build();
81+
Product superNES = Product.builder().id("id-3").name("Super Nintendo").popularity(3).build();
82+
Product nintendo64 = Product.builder().id("id-4").name("N64").description("Nintendo 64").popularity(2).build();
83+
84+
repository.saveAll(Arrays.asList(playstation, playstation2, superNES, nintendo64));
85+
}
7886
/**
7987
* {@link HighlightPage} holds next to the entities found also information about where a match was found within the
8088
* document. This allows to fine grained display snipplets of data containing the matching term in context.

solr/example/src/test/java/example/springdata/solr/BasicSolrRepositoryTests.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,15 @@
1515
*/
1616
package example.springdata.solr;
1717

18+
import example.springdata.solr.product.Product;
1819
import example.springdata.solr.product.ProductRepository;
19-
import example.springdata.solr.test.util.RequiresSolrServer;
20+
import example.springdata.test.util.InfrastructureRule;
2021

21-
import org.junit.ClassRule;
22+
import java.util.stream.IntStream;
23+
24+
import org.junit.After;
25+
import org.junit.Before;
26+
import org.junit.Rule;
2227
import org.junit.Test;
2328
import org.junit.runner.RunWith;
2429
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,15 +32,36 @@
2732

2833
/**
2934
* @author Christoph Strobl
35+
* @author Jens Schauder
3036
*/
3137
@RunWith(SpringRunner.class)
32-
@SpringBootTest(classes = SolrTestConfiguration.class)
38+
@SpringBootTest
3339
public class BasicSolrRepositoryTests {
3440

35-
public static @ClassRule RequiresSolrServer requiresRunningServer = RequiresSolrServer.onLocalhost();
41+
42+
43+
@Rule @Autowired public InfrastructureRule requiresRunningServer;
3644

3745
@Autowired ProductRepository repository;
3846

47+
/**
48+
* Remove test data when context is shut down.
49+
*/
50+
public @After void deleteDocumentsOnShutdown() {
51+
repository.deleteAll();
52+
}
53+
54+
/**
55+
* Initialize Solr instance with test data once context has started.
56+
*/
57+
public @Before void initWithTestData() {
58+
59+
repository.deleteAll();
60+
61+
IntStream.range(0, 100)
62+
.forEach(index -> repository.save(Product.builder().id("p-" + index).name("foobar").build()));
63+
}
64+
3965
/**
4066
* Finds all entries using a single request.
4167
*/

solr/example/src/test/java/example/springdata/solr/SolrTestConfiguration.java

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@
1616
package example.springdata.solr;
1717

1818
import example.springdata.solr.product.Product;
19-
20-
import java.util.stream.IntStream;
21-
22-
import javax.annotation.PostConstruct;
23-
import javax.annotation.PreDestroy;
19+
import example.springdata.solr.test.util.SolrInfrastructureRule;
20+
import example.springdata.test.util.InfrastructureRule;
2421

2522
import org.apache.solr.client.solrj.impl.HttpSolrClient;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
2625
import org.springframework.beans.factory.annotation.Autowired;
2726
import org.springframework.boot.autoconfigure.SpringBootApplication;
2827
import org.springframework.context.annotation.Bean;
@@ -32,33 +31,22 @@
3231
/**
3332
* @author Christoph Strobl
3433
* @author Oliver Gierke
34+
* @author Jens Schauder
3535
*/
3636
@SpringBootApplication
3737
public class SolrTestConfiguration {
3838

3939
@Autowired CrudRepository<Product, String> repo;
4040

41-
public @Bean SolrTemplate solrTemplate() {
42-
return new SolrTemplate(new HttpSolrClient.Builder().withBaseSolrUrl("http://localhost:8983/solr").build());
43-
}
41+
private static final Logger LOG = LoggerFactory.getLogger(SolrTestConfiguration.class);
4442

45-
/**
46-
* Remove test data when context is shut down.
47-
*/
48-
public @PreDestroy void deleteDocumentsOnShutdown() {
49-
repo.deleteAll();
50-
}
43+
public @Bean InfrastructureRule<String> infrastructureRule() {
5144

52-
/**
53-
* Initialize Solr instance with test data once context has started.
54-
*/
55-
public @PostConstruct void initWithTestData() {
56-
doInitTestData(repo);
45+
return new SolrInfrastructureRule("techproducts");
5746
}
5847

59-
protected void doInitTestData(CrudRepository<Product, String> repository) {
60-
61-
IntStream.range(0, 100)
62-
.forEach(index -> repository.save(Product.builder().id("p-" + index).name("foobar").build()));
48+
public @Bean SolrTemplate solrTemplate() {
49+
return new SolrTemplate(new HttpSolrClient.Builder().withBaseSolrUrl(infrastructureRule().getInfo()).build());
6350
}
51+
6452
}

0 commit comments

Comments
 (0)