Better answer late than never -
Unfortunately I found that Fongo does not address all of my requirements quite well - most notably, $eval
is not implemented so that you cannot run integration tests with migration tools such as Mongeez.
I settled for EmbedMongo, which I am using in my Spock/Geb integration tests via JUnit ExternalResource
rules. Even though Gary is right when he says that a real managed DB comes with many more moving parts, but I found that I'd rather take that risk than rely on a mock implementation. So far it worked quite well, give or take an unclean database shutdown during test suite teardown, which fortunately does not impact the tests. You would use the rules as follows:
@Integration(applicationClass = Application)
@TestFor(SomeGrailsArtifact) // this will inject grailsApplication
class SomeGrailsArtifactFunctionalSpec extends Specification {
@Shared @ClassRule
EmbedMongoRule embedMongoRule = new EmbedMongoRule(grailsApplication)
@Rule
ResetDatabaseRule resetDatabaseRule = new ResetDatabaseRule(embedMongoRule.db)
...
For the sake of completeness, these are the rule implementations:
EmbedMongoRule.groovy
import org.junit.rules.ExternalResource
import com.mongodb.MongoClient
import com.mongodb.MongoException
import de.flapdoodle.embed.mongo.MongodProcess
import de.flapdoodle.embed.mongo.MongodStarter
import de.flapdoodle.embed.mongo.config.IMongodConfig
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder
import de.flapdoodle.embed.mongo.config.Net
import de.flapdoodle.embed.mongo.distribution.Version
import de.flapdoodle.embed.process.runtime.Network
/**
* Rule for {@code EmbedMongo}, a managed full-fledged MongoDB. The first time
* this rule is used, it will download the current production MongoDB release,
* spin it up before tests and tear it down afterwards.
*
* @author Michael Jess
*
*/
public class EmbedMongoRule extends ExternalResource {
private def mongoConfig
private def mongodExecutable
public EmbedMongoRule(grailsApplication) {
if(!grailsApplication) {
throw new IllegalArgumentException(
"Got null grailsApplication; have you forgotten to supply it to the rule?\n" +
"\n" +
"@Integration(applicationClass = Application)\n" +
"@TestFor(MyGrailsArtifact)\n // will inject grailsApplication" +
"class MyGrailsArtifactSpec extends ... {\n" +
"\n" +
"\t..." +
"\t@Shared @ClassRule EmbedMongoRule embedMongoRule = new EmbedMongoRule(grailsApplication)\n" +
"\t...\n" +
"}")
}
mongoConfig = grailsApplication.config.grails.mongodb
}
@Override
protected void before() throws Throwable {
try {
MongodStarter starter = MongodStarter.getDefaultInstance()
IMongodConfig mongodConfig = new MongodConfigBuilder()
.version(Version.Main.PRODUCTION)
.net(new Net(mongoConfig.port, Network.localhostIsIPv6()))
.build()
mongodExecutable = starter.prepare(mongodConfig)
MongodProcess mongod = mongodExecutable.start()
} catch (IOException e) {
throw new IllegalStateException("Unable to start embedded mongo", e)
}
}
@Override
protected void after() {
mongodExecutable.stop()
}
/**
* Returns a new {@code DB} for the managed database.
*
* @return A new DB
* @throws IllegalStateException If an {@code UnknownHostException}
* or a {@code MongoException} occurs
*/
public def getDb() {
try {
return new MongoClient(mongoConfig.host, mongoConfig.port).getDB(mongoConfig.databaseName)
} catch (UnknownHostException | MongoException e) {
throw new IllegalStateException("Unable to retrieve MongoClient", e)
}
}
}
ResetDatabaseRule.groovy - currently not working since GORM ignores the grails.mongodb.databaseName
parameter as of org.grails.plugins:mongodb:4.0.0
(grails 3.x)
import org.junit.rules.ExternalResource
/**
* Rule that will clear whatever Mongo {@code DB} is provided.
* More specifically, all non-system collections are dropped from the database.
*
* @author Michael Jess
*
*/
public class ResetDatabaseRule extends ExternalResource {
/**
* Prefix identifying system tables
*/
private static final String SYSTEM_TABLE_PREFIX = "system"
private def db
/**
* Create a new database reset rule for the specified datastore.
*
* @param getDb Closure returning a reference to the {@link DB} instance
* to reset.
*/
ResetDatabaseRule(db) {
this.db = db
}
@Override
protected void before() throws Throwable {
db.collectionNames
.findAll { !it.startsWith(SYSTEM_TABLE_PREFIX) }
.each { db.getCollection(it).drop() }
}
}