Skip to content

GH-3839: Migrate MongoDB tests to Testcontainers #3852

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 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2020-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,14 +14,16 @@
* limitations under the License.
*/

package org.springframework.integration.mongodb.rules;
package org.springframework.integration.mongodb;

import java.time.Duration;

import org.bson.Document;
import org.bson.UuidRepresentation;
import org.bson.conversions.Bson;
import org.junit.Rule;
import org.junit.jupiter.api.BeforeAll;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.dao.DataAccessException;
import org.springframework.data.annotation.Id;
Expand All @@ -40,53 +42,68 @@
import org.springframework.integration.mongodb.outbound.MessageCollectionCallback;
import org.springframework.messaging.Message;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoException;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;


/**
* Convenience base class that enables unit test methods to rely upon the {@link MongoDbAvailable} annotation.
* The base contract for all tests requiring a MongoDb connection.
* The Testcontainers 'reuse' option must be disabled, so, Ryuk container is started
* and will clean all the containers up from this test suite after JVM exit.
* Since the Redis container instance is shared via static property, it is going to be
* started only once per JVM, therefore the target Docker container is reused automatically.
*
* @author Oleg Zhurakousky
* @author Xavier Padro
* @author Artem Bilan
* @author David Turanski
* @author Artem Vozhdayenko
*
* @since 2.1
* @since 6.0
*/
public abstract class MongoDbAvailableTests {

@Rule
public MongoDbAvailableRule mongoDbAvailableRule = new MongoDbAvailableRule();

public static final MongoDatabaseFactory MONGO_DATABASE_FACTORY =
new SimpleMongoClientDatabaseFactory(
MongoClients.create(
MongoClientSettings.builder().uuidRepresentation(UuidRepresentation.STANDARD).build()),
"test");

public static final ReactiveMongoDatabaseFactory REACTIVE_MONGO_DATABASE_FACTORY =
new SimpleReactiveMongoDatabaseFactory(
com.mongodb.reactivestreams.client.MongoClients.create(
MongoClientSettings.builder().uuidRepresentation(UuidRepresentation.STANDARD).build()),
"test");

protected MongoDatabaseFactory prepareMongoFactory(String... additionalCollectionsToDrop) {
cleanupCollections(MONGO_DATABASE_FACTORY, additionalCollectionsToDrop);
return MONGO_DATABASE_FACTORY;
@Testcontainers(disabledWithoutDocker = true)
public interface MongoDbContainerTest {
GenericContainer<?> MONGO_CONTAINER = new GenericContainer<>("mongo:5.0.9")
.withExposedPorts(27017);

@BeforeAll
static void startContainer() {
MONGO_CONTAINER.start();
}

static MongoDatabaseFactory createMongoDbFactory() {
return new SimpleMongoClientDatabaseFactory(
MongoClients.create(
MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(
"mongodb://localhost:" + MONGO_CONTAINER.getFirstMappedPort()))
.uuidRepresentation(UuidRepresentation.STANDARD).build()),
"test");
}

static ReactiveMongoDatabaseFactory createReactiveMongoDbFactory() {
return new SimpleReactiveMongoDatabaseFactory(
com.mongodb.reactivestreams.client.MongoClients.create(
MongoClientSettings.builder().applyConnectionString(new ConnectionString(
"mongodb://localhost:" + MONGO_CONTAINER.getFirstMappedPort()))
.uuidRepresentation(UuidRepresentation.STANDARD).build()),
"test");
}

static void prepareMongoData(MongoDatabaseFactory mongoDatabaseFactory, String... additionalCollectionsToDrop) {
cleanupCollections(mongoDatabaseFactory, additionalCollectionsToDrop);
}

protected ReactiveMongoDatabaseFactory prepareReactiveMongoFactory(String... additionalCollectionsToDrop) {
cleanupCollections(REACTIVE_MONGO_DATABASE_FACTORY, additionalCollectionsToDrop);
return REACTIVE_MONGO_DATABASE_FACTORY;
static void prepareReactiveMongoData(ReactiveMongoDatabaseFactory mongoDatabaseFactory, String... additionalCollectionsToDrop) {
cleanupCollections(mongoDatabaseFactory, additionalCollectionsToDrop);
}

protected void cleanupCollections(ReactiveMongoDatabaseFactory mongoDbFactory,
static void cleanupCollections(ReactiveMongoDatabaseFactory MONGO_DATABASE_FACTORY,
String... additionalCollectionsToDrop) {

ReactiveMongoTemplate template = new ReactiveMongoTemplate(mongoDbFactory);
ReactiveMongoTemplate template = new ReactiveMongoTemplate(MONGO_DATABASE_FACTORY);
template.dropCollection("messages").block(Duration.ofSeconds(3));
template.dropCollection("configurableStoreMessages").block(Duration.ofSeconds(3));
template.dropCollection("data").block(Duration.ofSeconds(3));
Expand All @@ -95,8 +112,8 @@ protected void cleanupCollections(ReactiveMongoDatabaseFactory mongoDbFactory,
}
}

protected void cleanupCollections(MongoDatabaseFactory mongoDbFactory, String... additionalCollectionsToDrop) {
MongoTemplate template = new MongoTemplate(mongoDbFactory);
static void cleanupCollections(MongoDatabaseFactory MONGO_DATABASE_FACTORY, String... additionalCollectionsToDrop) {
MongoTemplate template = new MongoTemplate(MONGO_DATABASE_FACTORY);
template.dropCollection("messages");
template.dropCollection("configurableStoreMessages");
template.dropCollection("data");
Expand All @@ -105,7 +122,7 @@ protected void cleanupCollections(MongoDatabaseFactory mongoDbFactory, String...
}
}

protected Person createPerson() {
static Person createPerson() {
Address address = new Address();
address.setCity("Philadelphia");
address.setStreet("2121 Rawn street");
Expand All @@ -117,7 +134,7 @@ protected Person createPerson() {
return person;
}

protected Person createPerson(String name) {
static Person createPerson(String name) {
Address address = new Address();
address.setCity("Philadelphia");
address.setStreet("2121 Rawn street");
Expand All @@ -129,7 +146,7 @@ protected Person createPerson(String name) {
return person;
}

public static class Person {
class Person {

@Id
private String id;
Expand All @@ -156,7 +173,7 @@ public void setName(String name) {

}

public static class Address {
class Address {

private String street;

Expand Down Expand Up @@ -190,13 +207,13 @@ public void setState(String state) {

}

public static class TestMongoConverter extends MappingMongoConverter {
class TestMongoConverter extends MappingMongoConverter {

public TestMongoConverter(
MongoDatabaseFactory mongoDbFactory,
MongoDatabaseFactory MONGO_DATABASE_FACTORY,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {

super(new DefaultDbRefResolver(mongoDbFactory), mappingContext);
super(new DefaultDbRefResolver(MONGO_DATABASE_FACTORY), mappingContext);
}

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

}

public static class ReactiveTestMongoConverter extends MappingMongoConverter {
class ReactiveTestMongoConverter extends MappingMongoConverter {

public ReactiveTestMongoConverter(
ReactiveMongoDatabaseFactory mongoDbFactory,
ReactiveMongoDatabaseFactory MONGO_DATABASE_FACTORY,
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {

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

}

public static class TestCollectionCallback implements MessageCollectionCallback<Long> {
class TestCollectionCallback implements MessageCollectionCallback<Long> {

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

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,39 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-mongodb="http://www.springframework.org/schema/integration/mongodb"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/data/mongo https://www.springframework.org/schema/data/mongo/spring-mongo.xsd
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
xsi:schemaLocation="http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration/mongodb https://www.springframework.org/schema/integration/mongodb/spring-integration-mongodb.xsd
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">

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

<int-mongodb:inbound-channel-adapter id="mongoInboundAdapter"
channel="replyChannel"
query="{'name' : 'Bob'}"
entity-class="java.lang.Object"
auto-startup="false">
<int:poller fixed-delay="100" />
<int:poller fixed-delay="100"/>
</int-mongodb:inbound-channel-adapter>

<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterNamedFactory"
mongodb-factory="mongoDbFactory"
channel="replyChannel"
query="{'name' : 'Bob'}"
auto-startup="false">
<int:poller fixed-delay="5000" />
<int:poller fixed-delay="5000"/>
</int-mongodb:inbound-channel-adapter>

<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterWithTemplate"
channel="replyChannel"
mongo-template="mongoDbTemplate"
query="{'name' : 'Bob'}"
expect-single-result="true"
entity-class="org.springframework.integration.mongodb.rules.MongoDbAvailableTests.Person"
entity-class="org.springframework.integration.mongodb.MongoDbContainerTest.Person"
auto-startup="false">
<int:poller fixed-delay="5000" />
<int:poller fixed-delay="5000"/>
</int-mongodb:inbound-channel-adapter>

<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterWithNamedCollection"
Expand All @@ -46,7 +45,7 @@
query="{'name' : 'Bob'}"
entity-class="java.lang.Object"
auto-startup="false">
<int:poller fixed-delay="5000" />
<int:poller fixed-delay="5000"/>
</int-mongodb:inbound-channel-adapter>
<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterWithStringQueryExpression"
channel="replyChannel"
Expand All @@ -55,7 +54,7 @@
query-expression="new String('{''name'' : ''Bob''}')"
entity-class="java.lang.Object"
auto-startup="false">
<int:poller fixed-rate="5000" />
<int:poller fixed-rate="5000"/>
</int-mongodb:inbound-channel-adapter>

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

<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterWithNamedCollectionExpression"
Expand All @@ -75,7 +74,7 @@
query="{'name' : 'Bob'}"
entity-class="java.lang.Object"
auto-startup="false">
<int:poller fixed-delay="5000" />
<int:poller fixed-delay="5000"/>
</int-mongodb:inbound-channel-adapter>

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

<int:poller fixed-delay="200" max-messages-per-poll="1">
<int:advice-chain synchronization-factory="syncFactory">
<int:advice-chain synchronization-factory="syncFactory">
<bean
class="org.springframework.integration.mongodb.config.MongoDbInboundChannelAdapterIntegrationTests.TestMessageSourceAdvice" />
class="org.springframework.integration.mongodb.config.MongoDbInboundChannelAdapterIntegrationTests.TestMessageSourceAdvice"/>
<tx:advice>
<tx:attributes>
<tx:method name="*" />
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
</int:advice-chain>
Expand All @@ -102,7 +101,7 @@
</int:transaction-synchronization-factory>

<int:channel id="afterCommitChannel">
<int:queue />
<int:queue/>
</int:channel>

<int-mongodb:inbound-channel-adapter id="mongoInboundAdapterWithConverter"
Expand All @@ -111,28 +110,28 @@
entity-class="java.lang.Object"
mongo-converter="mongoConverter"
auto-startup="false">
<int:poller fixed-delay="100" />
<int:poller fixed-delay="100"/>
</int-mongodb:inbound-channel-adapter>

<bean id="documentCleaner"
class="org.springframework.integration.mongodb.config.MongoDbInboundChannelAdapterIntegrationTests.DocumentCleaner" />
class="org.springframework.integration.mongodb.config.MongoDbInboundChannelAdapterIntegrationTests.DocumentCleaner"/>

<bean id="mongoDbTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongoDbFactory" />
<constructor-arg ref="mongoDbFactory"/>
</bean>

<bean id="mongoConverter"
class="org.springframework.integration.mongodb.rules.MongoDbAvailableTests.TestMongoConverter">
<constructor-arg ref="mongoDbFactory" />
class="org.springframework.integration.mongodb.MongoDbContainerTest.TestMongoConverter">
<constructor-arg ref="mongoDbFactory"/>
<constructor-arg>
<bean class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />
<bean class="org.springframework.data.mongodb.core.mapping.MongoMappingContext"/>
</constructor-arg>
</bean>

<int:channel id="replyChannel">
<int:queue />
<int:queue/>
</int:channel>

<bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager" />
<bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager"/>

</beans>
Loading