Skip to content

Commit 1520338

Browse files
committed
spring-projectsGH-3839: Migrate MongoDB tests to Testcontainers
Closes spring-projects#3839 * Create a new base interface for Mongo container-based tests * Migrate all tests to JUnit5 * Remove obsolete classes related to JUnit4 Mongo rule * Fix code style & readability here and there, a few Sonar issues * Fix failing test `validateWithConfiguredPollerFlow` * * This test was failing with Mongo error `$and/$or/$nor must be a nonempty array`. The cause was too small polling ratio of the update query, the consecutive read queries failed as no 'Oleg' document was in collection after ~100 ms.
1 parent 1c461a3 commit 1520338

File tree

36 files changed

+938
-1080
lines changed

36 files changed

+938
-1080
lines changed
Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2020-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,14 +14,16 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.integration.mongodb.rules;
17+
package org.springframework.integration.mongodb;
1818

1919
import java.time.Duration;
2020

2121
import org.bson.Document;
2222
import org.bson.UuidRepresentation;
2323
import org.bson.conversions.Bson;
24-
import org.junit.Rule;
24+
import org.junit.jupiter.api.BeforeAll;
25+
import org.testcontainers.containers.GenericContainer;
26+
import org.testcontainers.junit.jupiter.Testcontainers;
2527

2628
import org.springframework.dao.DataAccessException;
2729
import org.springframework.data.annotation.Id;
@@ -40,53 +42,68 @@
4042
import org.springframework.integration.mongodb.outbound.MessageCollectionCallback;
4143
import org.springframework.messaging.Message;
4244

45+
import com.mongodb.ConnectionString;
4346
import com.mongodb.MongoClientSettings;
4447
import com.mongodb.MongoException;
4548
import com.mongodb.client.MongoClients;
4649
import com.mongodb.client.MongoCollection;
4750

48-
4951
/**
50-
* Convenience base class that enables unit test methods to rely upon the {@link MongoDbAvailable} annotation.
52+
* The base contract for all tests requiring a MongoDb connection.
53+
* The Testcontainers 'reuse' option must be disabled, so, Ryuk container is started
54+
* and will clean all the containers up from this test suite after JVM exit.
55+
* Since the Redis container instance is shared via static property, it is going to be
56+
* started only once per JVM, therefore the target Docker container is reused automatically.
5157
*
5258
* @author Oleg Zhurakousky
5359
* @author Xavier Padro
5460
* @author Artem Bilan
5561
* @author David Turanski
62+
* @author Artem Vozhdayenko
5663
*
57-
* @since 2.1
64+
* @since 6.0
5865
*/
59-
public abstract class MongoDbAvailableTests {
60-
61-
@Rule
62-
public MongoDbAvailableRule mongoDbAvailableRule = new MongoDbAvailableRule();
63-
64-
public static final MongoDatabaseFactory MONGO_DATABASE_FACTORY =
65-
new SimpleMongoClientDatabaseFactory(
66-
MongoClients.create(
67-
MongoClientSettings.builder().uuidRepresentation(UuidRepresentation.STANDARD).build()),
68-
"test");
69-
70-
public static final ReactiveMongoDatabaseFactory REACTIVE_MONGO_DATABASE_FACTORY =
71-
new SimpleReactiveMongoDatabaseFactory(
72-
com.mongodb.reactivestreams.client.MongoClients.create(
73-
MongoClientSettings.builder().uuidRepresentation(UuidRepresentation.STANDARD).build()),
74-
"test");
75-
76-
protected MongoDatabaseFactory prepareMongoFactory(String... additionalCollectionsToDrop) {
77-
cleanupCollections(MONGO_DATABASE_FACTORY, additionalCollectionsToDrop);
78-
return MONGO_DATABASE_FACTORY;
66+
@Testcontainers(disabledWithoutDocker = true)
67+
public interface MongoDbContainerTest {
68+
GenericContainer<?> MONGO_CONTAINER = new GenericContainer<>("mongo:5.0.9")
69+
.withExposedPorts(27017);
70+
71+
@BeforeAll
72+
static void startContainer() {
73+
MONGO_CONTAINER.start();
74+
}
75+
76+
static MongoDatabaseFactory createMongoDbFactory() {
77+
return new SimpleMongoClientDatabaseFactory(
78+
MongoClients.create(
79+
MongoClientSettings.builder()
80+
.applyConnectionString(new ConnectionString(
81+
"mongodb://localhost:" + MONGO_CONTAINER.getFirstMappedPort()))
82+
.uuidRepresentation(UuidRepresentation.STANDARD).build()),
83+
"test");
84+
}
85+
86+
static ReactiveMongoDatabaseFactory createReactiveMongoDbFactory() {
87+
return new SimpleReactiveMongoDatabaseFactory(
88+
com.mongodb.reactivestreams.client.MongoClients.create(
89+
MongoClientSettings.builder().applyConnectionString(new ConnectionString(
90+
"mongodb://localhost:" + MONGO_CONTAINER.getFirstMappedPort()))
91+
.uuidRepresentation(UuidRepresentation.STANDARD).build()),
92+
"test");
93+
}
94+
95+
static void prepareMongoData(MongoDatabaseFactory mongoDatabaseFactory, String... additionalCollectionsToDrop) {
96+
cleanupCollections(mongoDatabaseFactory, additionalCollectionsToDrop);
7997
}
8098

81-
protected ReactiveMongoDatabaseFactory prepareReactiveMongoFactory(String... additionalCollectionsToDrop) {
82-
cleanupCollections(REACTIVE_MONGO_DATABASE_FACTORY, additionalCollectionsToDrop);
83-
return REACTIVE_MONGO_DATABASE_FACTORY;
99+
static void prepareReactiveMongoData(ReactiveMongoDatabaseFactory mongoDatabaseFactory, String... additionalCollectionsToDrop) {
100+
cleanupCollections(mongoDatabaseFactory, additionalCollectionsToDrop);
84101
}
85102

86-
protected void cleanupCollections(ReactiveMongoDatabaseFactory mongoDbFactory,
103+
static void cleanupCollections(ReactiveMongoDatabaseFactory MONGO_DATABASE_FACTORY,
87104
String... additionalCollectionsToDrop) {
88105

89-
ReactiveMongoTemplate template = new ReactiveMongoTemplate(mongoDbFactory);
106+
ReactiveMongoTemplate template = new ReactiveMongoTemplate(MONGO_DATABASE_FACTORY);
90107
template.dropCollection("messages").block(Duration.ofSeconds(3));
91108
template.dropCollection("configurableStoreMessages").block(Duration.ofSeconds(3));
92109
template.dropCollection("data").block(Duration.ofSeconds(3));
@@ -95,8 +112,8 @@ protected void cleanupCollections(ReactiveMongoDatabaseFactory mongoDbFactory,
95112
}
96113
}
97114

98-
protected void cleanupCollections(MongoDatabaseFactory mongoDbFactory, String... additionalCollectionsToDrop) {
99-
MongoTemplate template = new MongoTemplate(mongoDbFactory);
115+
static void cleanupCollections(MongoDatabaseFactory MONGO_DATABASE_FACTORY, String... additionalCollectionsToDrop) {
116+
MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY);
100117
template.dropCollection("messages");
101118
template.dropCollection("configurableStoreMessages");
102119
template.dropCollection("data");
@@ -105,7 +122,7 @@ protected void cleanupCollections(MongoDatabaseFactory mongoDbFactory, String...
105122
}
106123
}
107124

108-
protected Person createPerson() {
125+
static Person createPerson() {
109126
Address address = new Address();
110127
address.setCity("Philadelphia");
111128
address.setStreet("2121 Rawn street");
@@ -117,7 +134,7 @@ protected Person createPerson() {
117134
return person;
118135
}
119136

120-
protected Person createPerson(String name) {
137+
static Person createPerson(String name) {
121138
Address address = new Address();
122139
address.setCity("Philadelphia");
123140
address.setStreet("2121 Rawn street");
@@ -129,7 +146,7 @@ protected Person createPerson(String name) {
129146
return person;
130147
}
131148

132-
public static class Person {
149+
class Person {
133150

134151
@Id
135152
private String id;
@@ -156,7 +173,7 @@ public void setName(String name) {
156173

157174
}
158175

159-
public static class Address {
176+
class Address {
160177

161178
private String street;
162179

@@ -190,13 +207,13 @@ public void setState(String state) {
190207

191208
}
192209

193-
public static class TestMongoConverter extends MappingMongoConverter {
210+
class TestMongoConverter extends MappingMongoConverter {
194211

195212
public TestMongoConverter(
196-
MongoDatabaseFactory mongoDbFactory,
213+
MongoDatabaseFactory MONGO_DATABASE_FACTORY,
197214
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
198215

199-
super(new DefaultDbRefResolver(mongoDbFactory), mappingContext);
216+
super(new DefaultDbRefResolver(MONGO_DATABASE_FACTORY), mappingContext);
200217
}
201218

202219
@Override
@@ -211,10 +228,10 @@ public <S> S read(Class<S> clazz, Bson source) {
211228

212229
}
213230

214-
public static class ReactiveTestMongoConverter extends MappingMongoConverter {
231+
class ReactiveTestMongoConverter extends MappingMongoConverter {
215232

216233
public ReactiveTestMongoConverter(
217-
ReactiveMongoDatabaseFactory mongoDbFactory,
234+
ReactiveMongoDatabaseFactory MONGO_DATABASE_FACTORY,
218235
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
219236

220237
super(NoOpDbRefResolver.INSTANCE, mappingContext);
@@ -232,7 +249,7 @@ public <S> S read(Class<S> clazz, Bson source) {
232249

233250
}
234251

235-
public static class TestCollectionCallback implements MessageCollectionCallback<Long> {
252+
class TestCollectionCallback implements MessageCollectionCallback<Long> {
236253

237254
@Override
238255
public Long doInCollection(MongoCollection<Document> collection, Message<?> message)
@@ -242,5 +259,4 @@ public Long doInCollection(MongoCollection<Document> collection, Message<?> mess
242259
}
243260

244261
}
245-
246262
}

spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterIntegrationTests-context.xml

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,39 @@
33
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
44
xmlns:int="http://www.springframework.org/schema/integration"
55
xmlns:int-mongodb="http://www.springframework.org/schema/integration/mongodb"
6-
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
76
xmlns:tx="http://www.springframework.org/schema/tx"
8-
xsi:schemaLocation="http://www.springframework.org/schema/data/mongo https://www.springframework.org/schema/data/mongo/spring-mongo.xsd
9-
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
7+
xsi:schemaLocation="http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
108
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
119
http://www.springframework.org/schema/integration/mongodb https://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb.xsd
1210
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">
1311

14-
<mongo:db-factory id="mongoDbFactory" dbname="test" />
12+
<bean id="mongoDbFactory" class="org.springframework.integration.mongodb.MongoDbContainerTest"
13+
factory-method="createMongoDbFactory"/>
1514

1615
<int-mongodb:inbound-channel-adapter id="mongoInboundAdapter"
1716
channel="replyChannel"
1817
query="{'name' : 'Bob'}"
1918
entity-class="java.lang.Object"
2019
auto-startup="false">
21-
<int:poller fixed-delay="100" />
20+
<int:poller fixed-delay="100"/>
2221
</int-mongodb:inbound-channel-adapter>
2322

2423
<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterNamedFactory"
2524
mongodb-factory="mongoDbFactory"
2625
channel="replyChannel"
2726
query="{'name' : 'Bob'}"
2827
auto-startup="false">
29-
<int:poller fixed-delay="5000" />
28+
<int:poller fixed-delay="5000"/>
3029
</int-mongodb:inbound-channel-adapter>
3130

3231
<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterWithTemplate"
3332
channel="replyChannel"
3433
mongo-template="mongoDbTemplate"
3534
query="{'name' : 'Bob'}"
3635
expect-single-result="true"
37-
entity-class="org.springframework.integration.mongodb.rules.MongoDbAvailableTests.Person"
36+
entity-class="org.springframework.integration.mongodb.MongoDbContainerTest.Person"
3837
auto-startup="false">
39-
<int:poller fixed-delay="5000" />
38+
<int:poller fixed-delay="5000"/>
4039
</int-mongodb:inbound-channel-adapter>
4140

4241
<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterWithNamedCollection"
@@ -46,7 +45,7 @@
4645
query="{'name' : 'Bob'}"
4746
entity-class="java.lang.Object"
4847
auto-startup="false">
49-
<int:poller fixed-delay="5000" />
48+
<int:poller fixed-delay="5000"/>
5049
</int-mongodb:inbound-channel-adapter>
5150
<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterWithStringQueryExpression"
5251
channel="replyChannel"
@@ -55,7 +54,7 @@
5554
query-expression="new String('{''name'' : ''Bob''}')"
5655
entity-class="java.lang.Object"
5756
auto-startup="false">
58-
<int:poller fixed-rate="5000" />
57+
<int:poller fixed-rate="5000"/>
5958
</int-mongodb:inbound-channel-adapter>
6059

6160
<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterWithQueryExpression"
@@ -65,7 +64,7 @@
6564
query-expression="new BasicQuery('{''name'' : ''Bob''}').limit(1)"
6665
entity-class="java.lang.Object"
6766
auto-startup="false">
68-
<int:poller fixed-rate="5000" />
67+
<int:poller fixed-rate="5000"/>
6968
</int-mongodb:inbound-channel-adapter>
7069

7170
<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterWithNamedCollectionExpression"
@@ -75,7 +74,7 @@
7574
query="{'name' : 'Bob'}"
7675
entity-class="java.lang.Object"
7776
auto-startup="false">
78-
<int:poller fixed-delay="5000" />
77+
<int:poller fixed-delay="5000"/>
7978
</int-mongodb:inbound-channel-adapter>
8079

8180
<int-mongodb:inbound-channel-adapter id="inboundAdapterWithOnSuccessDisposition"
@@ -84,12 +83,12 @@
8483
auto-startup="false">
8584

8685
<int:poller fixed-delay="200" max-messages-per-poll="1">
87-
<int:advice-chain synchronization-factory="syncFactory">
86+
<int:advice-chain synchronization-factory="syncFactory">
8887
<bean
89-
class="org.springframework.integration.mongodb.config.MongoDbInboundChannelAdapterIntegrationTests.TestMessageSourceAdvice" />
88+
class="org.springframework.integration.mongodb.config.MongoDbInboundChannelAdapterIntegrationTests.TestMessageSourceAdvice"/>
9089
<tx:advice>
9190
<tx:attributes>
92-
<tx:method name="*" />
91+
<tx:method name="*"/>
9392
</tx:attributes>
9493
</tx:advice>
9594
</int:advice-chain>
@@ -102,7 +101,7 @@
102101
</int:transaction-synchronization-factory>
103102

104103
<int:channel id="afterCommitChannel">
105-
<int:queue />
104+
<int:queue/>
106105
</int:channel>
107106

108107
<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterWithConverter"
@@ -111,28 +110,28 @@
111110
entity-class="java.lang.Object"
112111
mongo-converter="mongoConverter"
113112
auto-startup="false">
114-
<int:poller fixed-delay="100" />
113+
<int:poller fixed-delay="100"/>
115114
</int-mongodb:inbound-channel-adapter>
116115

117116
<bean id="documentCleaner"
118-
class="org.springframework.integration.mongodb.config.MongoDbInboundChannelAdapterIntegrationTests.DocumentCleaner" />
117+
class="org.springframework.integration.mongodb.config.MongoDbInboundChannelAdapterIntegrationTests.DocumentCleaner"/>
119118

120119
<bean id="mongoDbTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
121-
<constructor-arg ref="mongoDbFactory" />
120+
<constructor-arg ref="mongoDbFactory"/>
122121
</bean>
123122

124123
<bean id="mongoConverter"
125-
class="org.springframework.integration.mongodb.rules.MongoDbAvailableTests.TestMongoConverter">
126-
<constructor-arg ref="mongoDbFactory" />
124+
class="org.springframework.integration.mongodb.MongoDbContainerTest.TestMongoConverter">
125+
<constructor-arg ref="mongoDbFactory"/>
127126
<constructor-arg>
128-
<bean class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />
127+
<bean class="org.springframework.data.mongodb.core.mapping.MongoMappingContext"/>
129128
</constructor-arg>
130129
</bean>
131130

132131
<int:channel id="replyChannel">
133-
<int:queue />
132+
<int:queue/>
134133
</int:channel>
135134

136-
<bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager" />
135+
<bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager"/>
137136

138137
</beans>

0 commit comments

Comments
 (0)