Skip to content

Commit be22534

Browse files
authored
Merge pull request #139 from ebean-orm/feature/add-mongodb-container
Feature/add mongodb container
2 parents 00f7673 + c9013e1 commit be22534

6 files changed

Lines changed: 329 additions & 1 deletion

File tree

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ containers to make testing nice for Ebean ORM, see https://ebean.io/docs/testing
1717

1818
## Supported Containers
1919

20-
Postgres, ClickHouse, CockroachDB, DB2, ElasticSearch, Floci, Hana, LocalDynamoDB, Localstack, MariaDB, MySql, NuoDB, Oracle, Postgres, Redis, SqlServer, Yugabyte.
20+
Postgres, ClickHouse, CockroachDB, DB2, ElasticSearch, Floci, Hana, LocalDynamoDB, Localstack, MariaDB, MongoDB, MySql, NuoDB, Oracle, Postgres, Redis, SqlServer, Yugabyte.
2121

2222
## Dependency
2323

@@ -289,6 +289,25 @@ occurring automatically on JVM shutdown.
289289

290290
```
291291

292+
#### MongoDB
293+
294+
```java
295+
MongoContainer container = MongoContainer.builder("8.0")
296+
//.port(27017)
297+
//.containerName("ut_mongodb")
298+
//.username("test")
299+
//.password("test")
300+
//.dbName("test")
301+
.start();
302+
303+
// obtain connection string
304+
String connectionString = container.connectionString();
305+
306+
// obtain a MongoClient (requires mongodb-driver-sync on the classpath)
307+
MongoClient client = container.mongoClient();
308+
309+
```
310+
292311
#### Oracle
293312

294313
```java

pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@
108108
<optional>true</optional>
109109
</dependency>
110110

111+
<dependency>
112+
<groupId>org.mongodb</groupId>
113+
<artifactId>mongodb-driver-sync</artifactId>
114+
<version>5.3.0</version>
115+
<scope>provided</scope>
116+
<optional>true</optional>
117+
</dependency>
118+
111119
<dependency>
112120
<groupId>io.ebean</groupId>
113121
<artifactId>ebean</artifactId>

src/main/java/io/ebean/test/containers/ContainerFactory.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ private void init() {
105105
if (db2Version != null) {
106106
containers.add(Db2Container.builder(db2Version).properties(properties).build());
107107
}
108+
String mongoVersion = runWithVersion("mongo");
109+
if (mongoVersion != null) {
110+
containers.add(MongoContainer.builder(mongoVersion).properties(properties).build());
111+
}
108112
}
109113

110114
/**
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package io.ebean.test.containers;
2+
3+
import com.mongodb.client.MongoClient;
4+
import com.mongodb.client.MongoClients;
5+
6+
import java.util.List;
7+
import java.util.Properties;
8+
9+
/**
10+
* MongoDB container.
11+
*
12+
* <pre>{@code
13+
*
14+
* MongoContainer container = MongoContainer.builder("8.0")
15+
* //.port(27017)
16+
* //.containerName("ut_mongodb")
17+
* //.username("test")
18+
* //.password("test")
19+
* //.dbName("test")
20+
* .build();
21+
*
22+
* container.start();
23+
*
24+
* // obtain connection string
25+
* String connectionString = container.connectionString();
26+
*
27+
* // obtain a MongoClient (requires mongodb-driver-sync on the classpath)
28+
* MongoClient client = container.mongoClient();
29+
* MongoDatabase db = client.getDatabase(container.dbName());
30+
*
31+
* // container will be shutdown and removed via shutdown hook
32+
* // local devs: touch ~/.ebean/ignore-docker-shutdown
33+
* // to keep the container running for faster testing
34+
*
35+
* }</pre>
36+
*
37+
* <h3>Shutdown</h3>
38+
* <p>
39+
* By default, the container will be stopped and removed via shutdown hook.
40+
*
41+
* <h3>Local development</h3>
42+
* <p>
43+
* For local development, keep the container running between test runs:
44+
*
45+
* <pre>
46+
* touch ~/.ebean/ignore-docker-shutdown
47+
* </pre>
48+
*/
49+
public class MongoContainer extends BaseContainer<MongoContainer> {
50+
51+
@Override
52+
public MongoContainer start() {
53+
startOrThrow();
54+
return this;
55+
}
56+
57+
/**
58+
* Create a builder for MongoContainer.
59+
*/
60+
public static Builder builder(String version) {
61+
return new Builder(version);
62+
}
63+
64+
/**
65+
* The MongoContainer builder.
66+
*/
67+
public static class Builder extends BaseBuilder<MongoContainer, Builder> {
68+
69+
private String username = "test";
70+
private String password = "test";
71+
private String dbName = "test";
72+
73+
private Builder(String version) {
74+
super("mongo", 27017, 27017, version);
75+
}
76+
77+
/**
78+
* Set the MongoDB admin username. Defaults to "test".
79+
*/
80+
public Builder username(String username) {
81+
this.username = username;
82+
return this;
83+
}
84+
85+
/**
86+
* Set the MongoDB admin password. Defaults to "test".
87+
*/
88+
public Builder password(String password) {
89+
this.password = password;
90+
return this;
91+
}
92+
93+
/**
94+
* Set the initial database name. Defaults to "test".
95+
*/
96+
public Builder dbName(String dbName) {
97+
this.dbName = dbName;
98+
return this;
99+
}
100+
101+
@Override
102+
protected void extraProperties(Properties properties) {
103+
username = prop(properties, "username", username);
104+
password = prop(properties, "password", password);
105+
dbName = prop(properties, "dbName", dbName);
106+
}
107+
108+
@Override
109+
public MongoContainer build() {
110+
return new MongoContainer(this);
111+
}
112+
113+
@Override
114+
public MongoContainer start() {
115+
return build().start();
116+
}
117+
}
118+
119+
private final String username;
120+
private final String password;
121+
private final String dbName;
122+
123+
private MongoContainer(Builder builder) {
124+
super(builder);
125+
this.username = builder.username;
126+
this.password = builder.password;
127+
this.dbName = builder.dbName;
128+
}
129+
130+
/**
131+
* Return the database name.
132+
*/
133+
public String dbName() {
134+
return dbName;
135+
}
136+
137+
/**
138+
* Return the MongoDB connection string for this container.
139+
*/
140+
public String connectionString() {
141+
if (notEmpty(username) && notEmpty(password)) {
142+
return String.format("mongodb://%s:%s@%s:%d/%s", username, password, config.getHost(), config.getPort(), dbName);
143+
}
144+
return String.format("mongodb://%s:%d/%s", config.getHost(), config.getPort(), dbName);
145+
}
146+
147+
/**
148+
* Return a MongoClient connected to this container.
149+
* <p>
150+
* Requires {@code mongodb-driver-sync} on the classpath.
151+
*/
152+
public MongoClient mongoClient() {
153+
return MongoClients.create(connectionString());
154+
}
155+
156+
@Override
157+
boolean checkConnectivity() {
158+
// clearMatch discards the first "Waiting for connections" that appears during
159+
// the credential-setup phase (binds to 127.0.0.1 only) so we wait for the
160+
// second occurrence that binds to 0.0.0.0 and is actually reachable.
161+
// When no auth is configured the clearMatch never triggers, but the single
162+
// "Waiting for connections" still matches — so this is correct in both cases.
163+
return logsContain("Waiting for connections", "MongoDB init process complete");
164+
}
165+
166+
@Override
167+
protected ProcessBuilder runProcess() {
168+
List<String> args = dockerRun();
169+
if (notEmpty(username)) {
170+
args.add("-e");
171+
args.add("MONGO_INITDB_ROOT_USERNAME=" + username);
172+
}
173+
if (notEmpty(password)) {
174+
args.add("-e");
175+
args.add("MONGO_INITDB_ROOT_PASSWORD=" + password);
176+
}
177+
if (notEmpty(dbName)) {
178+
args.add("-e");
179+
args.add("MONGO_INITDB_DATABASE=" + dbName);
180+
}
181+
args.add(config.image());
182+
return createProcessBuilder(args);
183+
}
184+
}

src/main/java9/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@
1212
// requires static aws.java.sdk.s3;
1313
// requires static aws.java.sdk.sns;
1414
// requires static aws.java.sdk.sqs;
15+
// requires static org.mongodb.driver.sync.client;
1516
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package io.ebean.test.containers;
2+
3+
import com.mongodb.client.MongoClient;
4+
import com.mongodb.client.MongoDatabase;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.util.Properties;
8+
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
import static org.junit.jupiter.api.Assertions.assertTrue;
12+
13+
class MongoContainerTest {
14+
15+
@Test
16+
void randomPort() {
17+
MongoContainer container = MongoContainer.builder("8.0")
18+
.port(0)
19+
.build();
20+
21+
assertTrue(container.startMaybe());
22+
assertThat(container.port()).isGreaterThan(0);
23+
assertThat(container.connectionString()).contains(":" + container.port() + "/");
24+
}
25+
26+
@Test
27+
void checkConnectivity() {
28+
MongoContainer container = MongoContainer.builder("8.0")
29+
.port(27017)
30+
.containerName("temp_mongo")
31+
.build();
32+
33+
assertTrue(container.startMaybe());
34+
35+
try (MongoClient client = container.mongoClient()) {
36+
MongoDatabase db = client.getDatabase(container.dbName());
37+
assertThat(db.getName()).isEqualTo("test");
38+
}
39+
40+
container.stopRemove();
41+
}
42+
43+
@Test
44+
void connectionString_withAuth() {
45+
MongoContainer container = MongoContainer.builder("8.0")
46+
.username("admin")
47+
.password("secret")
48+
.dbName("mydb")
49+
.build();
50+
51+
assertThat(container.connectionString()).isEqualTo("mongodb://admin:secret@localhost:27017/mydb");
52+
}
53+
54+
@Test
55+
void connectionString_noAuth() {
56+
MongoContainer container = MongoContainer.builder("8.0")
57+
.username("")
58+
.password("")
59+
.dbName("mydb")
60+
.build();
61+
62+
assertThat(container.connectionString()).isEqualTo("mongodb://localhost:27017/mydb");
63+
}
64+
65+
@Test
66+
void properties_with_noPrefix() {
67+
Properties properties = new Properties();
68+
properties.setProperty("mongo.image", "mongo:7.0");
69+
properties.setProperty("mongo.port", "27018");
70+
properties.setProperty("mongo.containerName", "mongo_junk8");
71+
properties.setProperty("mongo.internalPort", "27017");
72+
properties.setProperty("mongo.username", "admin");
73+
properties.setProperty("mongo.password", "secret");
74+
properties.setProperty("mongo.dbName", "mydb");
75+
properties.setProperty("mongo.shutdownMode", "stop");
76+
77+
InternalConfig config = MongoContainer.builder("8.0")
78+
.properties(properties)
79+
.internalConfig();
80+
config.setDefaultContainerName();
81+
82+
assertProperties(config);
83+
}
84+
85+
@Test
86+
void properties_with_ebeanTestPrefix() {
87+
Properties properties = new Properties();
88+
properties.setProperty("ebean.test.mongo.image", "mongo:7.0");
89+
properties.setProperty("ebean.test.mongo.port", "27018");
90+
properties.setProperty("ebean.test.mongo.containerName", "mongo_junk8");
91+
properties.setProperty("ebean.test.mongo.internalPort", "27017");
92+
properties.setProperty("ebean.test.mongo.username", "admin");
93+
properties.setProperty("ebean.test.mongo.password", "secret");
94+
properties.setProperty("ebean.test.mongo.dbName", "mydb");
95+
properties.setProperty("ebean.test.mongo.shutdownMode", "stop");
96+
97+
InternalConfig config = MongoContainer.builder("8.0")
98+
.properties(properties)
99+
.internalConfig();
100+
config.setDefaultContainerName();
101+
102+
assertProperties(config);
103+
}
104+
105+
private void assertProperties(InternalConfig config) {
106+
assertEquals(27018, config.getPort());
107+
assertEquals(27017, config.getInternalPort());
108+
assertEquals("mongo:7.0", config.getImage());
109+
assertEquals(StopMode.Stop, config.shutdownMode());
110+
assertEquals("mongo_junk8", config.containerName());
111+
}
112+
}

0 commit comments

Comments
 (0)