Reputation: 41
I'm currently using mongoDB and I wanted to be available to run integration and funcional tests on any machine (currently in dedicated build server and in a future CI server).
The main problem is that I have to be able to check mongodb installation (and if not present, install it), start a mongodb instance on startup and shut it down once the process has finished.
There's an already developed question here Embedded MongoDB when running integration tests that suggests installing a gradle or maven plugin.
This gradle plugin https://github.com/sourcemuse/GradleMongoPlugin/ can do this, but I will have to manage my dependencies with it, already tried. The problem with this approach is not gradle itself, but when tried this I've lost all the benefits from my IDE (STS, intellij).
Did anyone managed to do this?
If someone configured gradle with a grails project withour losing the grails perspective, I will appreciate that help too!
Thanks!
Trygve.
Upvotes: 1
Views: 1181
Reputation: 12228
I have recently created a grails plugin for this purpose. https://github.com/grails-plugins/grails-embedded-mongodb
Currently it is in snapshot, however I plan to publish a release this week
Upvotes: 2
Reputation: 1907
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() }
}
}
Upvotes: 0
Reputation: 6667
I've had good results using an in-memory Mongo server for integration tests. It runs fast and doesn't require starting up a separate Mongo server or dealing with special grails or maven config. This means that the tests can run equally well with any JUnit test runner, i.e. within any IDE or build system. No extra setup required.
I have also used the "flapdoodle" embedded mongo server for testing. It uses a different approach in that it downloads and executes a separate process for a real Mongo instance. I have found that this mechanism has more moving parts and seems to be overkill for me when all I really want to do is verify that my app works correctly with a mongo server.
Upvotes: 0