diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDB.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDB.java
index 953f4b1bf..880b6a217 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDB.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDB.java
@@ -18,6 +18,7 @@ package org.springframework.data.mongodb.test.util;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@@ -27,25 +28,35 @@ import org.junit.runners.model.Statement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
import com.mongodb.DB;
+import com.mongodb.DBCollection;
import com.mongodb.MongoClient;
/**
+ * {@link CleanMongoDB} is a junit {@link TestRule} implementation to be used as for wiping data from MongoDB instance.
+ * MongoDB specific system databases like {@literal admin} and {@literal local} remain untouched. The rule will apply
+ * after the base {@link Statement}.
+ * Use as {@link org.junit.ClassRule} to wipe data after finishing all tests within a class or as {@link org.junit.Rule}
+ * to do so after each {@link org.junit.Test}.
+ *
* @author Christoph Strobl
+ * @since 1.6
*/
public class CleanMongoDB implements TestRule {
private static final Logger LOGGER = LoggerFactory.getLogger(CleanMongoDB.class);
- public enum Types {
+ /**
+ * Defines contents of MongoDB.
+ */
+ public enum Struct {
DATABASE, COLLECTION, INDEX;
}
+ @SuppressWarnings("serial")//
private Set preserveDatabases = new HashSet() {
-
- private static final long serialVersionUID = -8698807376808700046L;
-
{
add("admin");
add("local");
@@ -54,57 +65,278 @@ public class CleanMongoDB implements TestRule {
private Set dbNames = new HashSet();
private Set collectionNames = new HashSet();
- private Set types = new HashSet();
+ private Set types = new HashSet();
private MongoClient client;
+ /**
+ * Create new instance using an internal {@link MongoClient}.
+ */
public CleanMongoDB() {
this(null);
}
+ /**
+ * Create new instance using an internal {@link MongoClient} connecting to specified instance running at host:port.
+ *
+ * @param host
+ * @param port
+ * @throws UnknownHostException
+ */
public CleanMongoDB(String host, int port) throws UnknownHostException {
this(new MongoClient(host, port));
}
+ /**
+ * Create new instance using the given client.
+ *
+ * @param client
+ */
public CleanMongoDB(MongoClient client) {
this.client = client;
}
+ /**
+ * Removes everything by dropping every single {@link DB}.
+ *
+ * @return
+ */
public static CleanMongoDB everything() {
CleanMongoDB cleanMongoDB = new CleanMongoDB();
- cleanMongoDB.clean(Types.DATABASE);
+ cleanMongoDB.clean(Struct.DATABASE);
return cleanMongoDB;
}
+ /**
+ * Removes everything from the databases with given name by dropping the according {@link DB}.
+ *
+ * @param dbNames
+ * @return
+ */
public static CleanMongoDB databases(String... dbNames) {
CleanMongoDB cleanMongoDB = new CleanMongoDB();
- cleanMongoDB.clean(Types.DATABASE);
- cleanMongoDB.collectionNames.addAll(Arrays.asList(dbNames));
+ cleanMongoDB.clean(Struct.DATABASE);
+ cleanMongoDB.useDatabases(dbNames);
+ return cleanMongoDB;
+ }
+
+ /**
+ * Drops the {@link DBCollection} with given names from every single {@link DB} containing them.
+ *
+ * @param collectionNames
+ * @return
+ */
+ public static CleanMongoDB collections(String... collectionNames) {
+ return collections("", Arrays.asList(collectionNames));
+ }
+
+ /**
+ * Drops the {@link DBCollection} with given names from the named {@link DB}.
+ *
+ * @param dbName
+ * @param collectionNames
+ * @return
+ */
+ public static CleanMongoDB collections(String dbName, Collection collectionNames) {
+
+ CleanMongoDB cleanMongoDB = new CleanMongoDB();
+ cleanMongoDB.clean(Struct.COLLECTION);
+ cleanMongoDB.useCollections(dbName, collectionNames);
return cleanMongoDB;
}
+ /**
+ * Drops all index structures from every single {@link DBCollection}.
+ *
+ * @return
+ */
public static CleanMongoDB indexes() {
+ return indexes(Collections. emptySet());
+ }
+
+ /**
+ * Drops all index structures from every single {@link DBCollection}.
+ *
+ * @param collectionNames
+ * @return
+ */
+ public static CleanMongoDB indexes(Collection collectionNames) {
CleanMongoDB cleanMongoDB = new CleanMongoDB();
- cleanMongoDB.clean(Types.INDEX);
+ cleanMongoDB.clean(Struct.INDEX);
+ cleanMongoDB.useCollections(collectionNames);
return cleanMongoDB;
}
- public CleanMongoDB clean(Types... types) {
+ /**
+ * Define {@link Struct} to be cleaned.
+ *
+ * @param types
+ * @return
+ */
+ public CleanMongoDB clean(Struct... types) {
this.types.addAll(Arrays.asList(types));
return this;
}
- public Statement apply() {
+ /**
+ * Defines the {@link DB}s to be used.
+ * Impact along with {@link CleanMongoDB#clean(Struct...)}:
+ *
+ * - {@link Struct#DATABASE}: Forces drop of named databases.
+ * - {@link Struct#COLLECTION}: Forces drop of collections within named databases.
+ * - {@link Struct#INDEX}: Removes index within collections of named databases.
+ *
+ *
+ * @param dbNames
+ * @return
+ */
+ public CleanMongoDB useDatabases(String... dbNames) {
+
+ this.dbNames.addAll(Arrays.asList(dbNames));
+ return this;
+ }
+
+ /**
+ * Excludes the given {@link DB}s from being processed.
+ *
+ * @param dbNames
+ * @return
+ */
+ public CleanMongoDB preserveDatabases(String... dbNames) {
+ this.preserveDatabases.addAll(Arrays.asList(dbNames));
+ return this;
+ }
+
+ /**
+ * Defines the {@link DBCollection}s to be used.
+ * Impact along with {@link CleanMongoDB#clean(Struct...)}:
+ *
+ * - {@link Struct#COLLECTION}: Forces drop of named collections.
+ * - {@link Struct#INDEX}: Removes index within named collections.
+ *
+ *
+ * @param collectionNames
+ * @return
+ */
+ public CleanMongoDB useCollections(String... collectionNames) {
+ return useCollections(Arrays.asList(collectionNames));
+ }
+
+ private CleanMongoDB useCollections(Collection collectionNames) {
+ return useCollections("", collectionNames);
+ }
+
+ /**
+ * Defines the {@link DBCollection}s and {@link DB} to be used.
+ * Impact along with {@link CleanMongoDB#clean(Struct...)}:
+ *
+ * - {@link Struct#COLLECTION}: Forces drop of named collections in given db.
+ * - {@link Struct#INDEX}: Removes index within named collections in given db.
+ *
+ *
+ * @param collectionNames
+ * @return
+ */
+ public CleanMongoDB useCollections(String db, Collection collectionNames) {
+
+ if (StringUtils.hasText(db)) {
+ this.dbNames.add(db);
+ }
+
+ if (!CollectionUtils.isEmpty(collectionNames)) {
+ this.collectionNames.addAll(collectionNames);
+ }
+ return this;
+ }
+
+ Statement apply() {
return apply(null, null);
}
+ /*
+ * (non-Javadoc)
+ * @see org.junit.rules.TestRule#apply(org.junit.runners.model.Statement, org.junit.runner.Description)
+ */
public Statement apply(Statement base, Description description) {
return new MongoCleanStatement(base);
}
+ private void doClean() {
+
+ Collection dbNamesToUse = initDbNames();
+
+ for (String dbName : dbNamesToUse) {
+
+ if (isPreserved(dbName) || dropDbIfRequired(dbName)) {
+ continue;
+ }
+
+ DB db = client.getDB(dbName);
+ dropCollectionsOrIndexIfRequried(db, initCollectionNames(db));
+ }
+ }
+
+ private boolean dropDbIfRequired(String dbName) {
+
+ if (!types.contains(Struct.DATABASE)) {
+ return false;
+ }
+
+ client.dropDatabase(dbName);
+ LOGGER.debug("Dropping DB '{}'. ", dbName);
+ return true;
+ }
+
+ private void dropCollectionsOrIndexIfRequried(DB db, Collection collectionsToUse) {
+
+ for (String collectionName : collectionsToUse) {
+
+ if (db.collectionExists(collectionName)) {
+
+ DBCollection collection = db.getCollectionFromString(collectionName);
+ if (collection != null) {
+
+ if (types.contains(Struct.COLLECTION)) {
+ collection.drop();
+ LOGGER.debug("Dropping collection '{}' for DB '{}'. ", collectionName, db.getName());
+ } else if (types.contains(Struct.INDEX)) {
+ collection.dropIndexes();
+ LOGGER.debug("Dropping indexes in collection '{}' for DB '{}'. ", collectionName, db.getName());
+ }
+ }
+ }
+ }
+ }
+
+ private boolean isPreserved(String dbName) {
+ return preserveDatabases.contains(dbName.toLowerCase());
+ }
+
+ private Collection initDbNames() {
+
+ Collection dbNamesToUse = dbNames;
+ if (dbNamesToUse.isEmpty()) {
+ dbNamesToUse = client.getDatabaseNames();
+ }
+ return dbNamesToUse;
+ }
+
+ private Collection initCollectionNames(DB db) {
+
+ Collection collectionsToUse = collectionNames;
+ if (CollectionUtils.isEmpty(collectionsToUse)) {
+ collectionsToUse = db.getCollectionNames();
+ }
+ return collectionsToUse;
+ }
+
+ /**
+ * @author Christoph Strobl
+ * @since 1.6
+ */
private class MongoCleanStatement extends Statement {
private final Statement base;
@@ -126,61 +358,12 @@ public class CleanMongoDB implements TestRule {
isInternal = true;
}
- Collection dbNamesToUse = dbNames;
- if (dbNamesToUse.isEmpty()) {
- dbNamesToUse = client.getDatabaseNames();
- }
-
- for (String dbName : dbNamesToUse) {
-
- if (preserveDatabases.contains(dbName.toLowerCase())) {
- continue;
- }
-
- if (types.contains(Types.DATABASE)) {
- client.dropDatabase(dbName);
- LOGGER.debug("Dropping DB '{}'. ", dbName);
- }
-
- if (types.contains(Types.COLLECTION)) {
-
- DB db = client.getDB(dbName);
- Collection collectionsToUse = initCollectionNames(db);
- for (String collectionName : collectionsToUse) {
- if (db.collectionExists(collectionName)) {
- db.getCollectionFromString(collectionName).drop();
- LOGGER.debug("Dropping collection '{}' for DB '{}'. ", collectionName, dbName);
- }
- }
- }
-
- if (types.contains(Types.INDEX)) {
-
- DB db = client.getDB(dbName);
- Collection collectionsToUse = initCollectionNames(db);
- for (String collectionName : collectionsToUse) {
- if (db.collectionExists(collectionName)) {
- db.getCollectionFromString(collectionName).dropIndexes();
- LOGGER.debug("Dropping indexes in collection '{}' for DB '{}'. ", collectionName, dbName);
- }
- }
- }
- }
+ doClean();
if (isInternal) {
client.close();
client = null;
}
}
-
- private Collection initCollectionNames(DB db) {
-
- Collection collectionsToUse = collectionNames;
- if (CollectionUtils.isEmpty(collectionsToUse)) {
- collectionsToUse = db.getCollectionNames();
- }
- return collectionsToUse;
- }
}
-
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDBJunitRunListener.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDBJunitRunListener.java
index a5f0e90ef..ce2beefc9 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDBJunitRunListener.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDBJunitRunListener.java
@@ -17,21 +17,24 @@ package org.springframework.data.mongodb.test.util;
import org.junit.runner.Result;
import org.junit.runner.notification.RunListener;
-import org.springframework.data.mongodb.test.util.CleanMongoDB.Types;
+import org.springframework.data.mongodb.test.util.CleanMongoDB.Struct;
/**
+ * {@link RunListener} implementation to be used for wiping MongoDB index structures after all test runs have finished.
+ *
* @author Christoph Strobl
+ * @since 1.6
*/
public class CleanMongoDBJunitRunListener extends RunListener {
@Override
public void testRunFinished(Result result) throws Exception {
+
super.testRunFinished(result);
try {
- new CleanMongoDB().clean(Types.INDEX).apply().evaluate();
+ new CleanMongoDB().clean(Struct.INDEX).apply().evaluate();
} catch (Throwable e) {
e.printStackTrace();
}
}
-
}
diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDBTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDBTests.java
index 5b600e905..209dd61c0 100644
--- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDBTests.java
+++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/test/util/CleanMongoDBTests.java
@@ -20,6 +20,7 @@ import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashSet;
import org.junit.Before;
import org.junit.Test;
@@ -28,7 +29,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.model.Statement;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
-import org.springframework.data.mongodb.test.util.CleanMongoDB.Types;
+import org.springframework.data.mongodb.test.util.CleanMongoDB.Struct;
import com.mongodb.DB;
import com.mongodb.DBCollection;
@@ -41,58 +42,146 @@ import com.mongodb.MongoClient;
public class CleanMongoDBTests {
private CleanMongoDB cleaner;
+
+ // JUnit internals
private @Mock Statement baseStatementMock;
private @Mock Description descriptionMock;
+
+ // MongoClient in use
private @Mock MongoClient mongoClientMock;
- private @Mock DB db1mock;
- private @Mock DB db2mock;
- private @Mock DBCollection collection1mock;
+ // Some Mock DBs
+ private @Mock DB db1mock, db2mock;
+ private @Mock DBCollection db1collection1mock, db1collection2mock, db2collection1mock;
+
+ @SuppressWarnings("serial")
@Before
public void setUp() {
+ // DB setup
when(mongoClientMock.getDatabaseNames()).thenReturn(Arrays.asList("admin", "db1", "db2"));
when(mongoClientMock.getDB(eq("db1"))).thenReturn(db1mock);
when(mongoClientMock.getDB(eq("db2"))).thenReturn(db2mock);
+
+ // collections have to exist
when(db1mock.collectionExists(anyString())).thenReturn(true);
when(db2mock.collectionExists(anyString())).thenReturn(true);
- when(db1mock.getCollectionNames()).thenReturn(Collections.singleton("collection-1"));
- when(db2mock.getCollectionNames()).thenReturn(Collections. emptySet());
- when(db1mock.getCollectionFromString(eq("collection-1"))).thenReturn(collection1mock);
+
+ // init collection names per database
+ when(db1mock.getCollectionNames()).thenReturn(new HashSet() {
+ {
+ add("db1collection1");
+ add("db1collection2");
+ }
+ });
+ when(db2mock.getCollectionNames()).thenReturn(Collections.singleton("db2collection1"));
+
+ // return collections according to names
+ when(db1mock.getCollectionFromString(eq("db1collection1"))).thenReturn(db1collection1mock);
+ when(db1mock.getCollectionFromString(eq("db1collection2"))).thenReturn(db1collection2mock);
+ when(db2mock.getCollectionFromString(eq("db2collection1"))).thenReturn(db2collection1mock);
cleaner = new CleanMongoDB(mongoClientMock);
}
@Test
- public void preservesSystemCollectionsCorrectly() throws Throwable {
+ public void preservesSystemDBsCorrectlyWhenCleaningDatabase() throws Throwable {
+
+ cleaner.clean(Struct.DATABASE);
+
+ cleaner.apply(baseStatementMock, descriptionMock).evaluate();
+
+ verify(mongoClientMock, never()).dropDatabase(eq("admin"));
+ }
+
+ @Test
+ public void preservesNamedDBsCorrectlyWhenCleaningDatabase() throws Throwable {
- cleaner.clean(Types.DATABASE);
+ cleaner.clean(Struct.DATABASE);
+ cleaner.preserveDatabases("db1");
+
+ cleaner.apply(baseStatementMock, descriptionMock).evaluate();
+
+ verify(mongoClientMock, never()).dropDatabase(eq("db1"));
+ }
+
+ @Test
+ public void dropsAllDBsCorrectlyWhenCleaingDatabaseAndNotExplictDBNamePresent() throws Throwable {
+
+ cleaner.clean(Struct.DATABASE);
cleaner.apply(baseStatementMock, descriptionMock).evaluate();
verify(mongoClientMock, times(1)).dropDatabase(eq("db1"));
verify(mongoClientMock, times(1)).dropDatabase(eq("db2"));
- verify(mongoClientMock, never()).dropDatabase(eq("admin"));
}
@Test
- public void removesCollectionsCorrectly() throws Throwable {
+ public void dropsSpecifiedDBsCorrectlyWhenExplicitNameSet() throws Throwable {
+
+ cleaner.clean(Struct.DATABASE);
+ cleaner.useDatabases("db2");
+
+ cleaner.apply(baseStatementMock, descriptionMock).evaluate();
+
+ verify(mongoClientMock, times(1)).dropDatabase(eq("db2"));
+ verify(mongoClientMock, never()).dropDatabase(eq("db1"));
+ }
+
+ @Test
+ public void doesNotRemoveAnyDBwhenCleaningCollections() throws Throwable {
- cleaner.clean(Types.COLLECTION);
+ cleaner.clean(Struct.COLLECTION);
cleaner.apply(baseStatementMock, descriptionMock).evaluate();
verify(mongoClientMock, never()).dropDatabase(eq("db1"));
verify(mongoClientMock, never()).dropDatabase(eq("db2"));
verify(mongoClientMock, never()).dropDatabase(eq("admin"));
+ }
+
+ @Test
+ public void doesNotDropCollectionsFromPreservedDBs() throws Throwable {
+
+ cleaner.clean(Struct.COLLECTION);
+ cleaner.preserveDatabases("db1");
+
+ cleaner.apply(baseStatementMock, descriptionMock).evaluate();
+
+ verify(db1collection1mock, never()).drop();
+ verify(db1collection2mock, never()).drop();
+ verify(db2collection1mock, times(1)).drop();
+ }
+
+ @Test
+ public void removesAllCollectionsFromAllDatabasesWhenNotLimitedToSpecificOnes() throws Throwable {
+
+ cleaner.clean(Struct.COLLECTION);
+
+ cleaner.apply(baseStatementMock, descriptionMock).evaluate();
+
+ verify(db1collection1mock, times(1)).drop();
+ verify(db1collection2mock, times(1)).drop();
+ verify(db2collection1mock, times(1)).drop();
+ }
+
+ @Test
+ public void removesOnlyNamedCollectionsWhenSpecified() throws Throwable {
+
+ cleaner.clean(Struct.COLLECTION);
+ cleaner.useCollections("db1collection2");
+
+ cleaner.apply(baseStatementMock, descriptionMock).evaluate();
- verify(collection1mock, times(1)).drop();
+ verify(db1collection1mock, never()).drop();
+ verify(db2collection1mock, never()).drop();
+ verify(db1collection2mock, times(1)).drop();
}
@Test
public void removesIndexesCorrectly() throws Throwable {
- cleaner.clean(Types.INDEX);
+ cleaner.clean(Struct.INDEX);
cleaner.apply(baseStatementMock, descriptionMock).evaluate();
@@ -100,6 +189,6 @@ public class CleanMongoDBTests {
verify(mongoClientMock, never()).dropDatabase(eq("db2"));
verify(mongoClientMock, never()).dropDatabase(eq("admin"));
- verify(collection1mock, times(1)).dropIndexes();
+ verify(db1collection1mock, times(1)).dropIndexes();
}
}