1

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.

Community
  • 1
  • 1

3 Answers3

2

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

James Kleeh
  • 12,094
  • 5
  • 34
  • 61
  • Can you add instructions on how to use this plugin after installation ? Example test case ? – Anuj Kulkarni Dec 06 '16 at 15:29
  • @AnujKu Other than configuration, which is documented, there are no instructions, it just works. – James Kleeh Dec 12 '16 at 19:21
  • I've used this plugin with success, I did have to add an additional dependency using Grails 3.1.8 however `compile 'org.apache.commons:commons-compress:1.17`, without it I got a NoClassDefFound for `org/apache/commons/compress/archivers/zip/ZipFile` – Mike W Jun 27 '18 at 07:48
0

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.

In-memory Mongo example

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.

Gary
  • 6,357
  • 5
  • 30
  • 36
  • Hey, great approach! Since my testing consists in specific mongodb geodatabase queries, I thought I needed a real database instance running with grails GORM features. Do you think I can accomplish this with your solution? I've managed to run the gradle plugin into a different project and the grails app separately. I will later come to your solution once I had some extra time, Thanks! – Trygve Korssjen May 30 '14 at 20:03
  • Does Fongo work well plugins like grails.org/plugin/fixtures? – Alexander Suraphel Nov 21 '14 at 15:01
0

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() }
    }

}
Michael Jess
  • 1,907
  • 1
  • 19
  • 17
  • Michael, Do you have this in an example project somewhere? I'm getting an exception simply after adding the embed mongo to my build.gradle. – James Kleeh Jun 09 '16 at 21:03
  • @JamesKleeh Unfortunately not. What I had is part of a much larger project and we have long stepped away from MongoDB as it turned out it did not fit the nature of our data quite well. For what it's worth, I checked our repo and the last working setup I had was using ``de.flapdoodle.embed.mongo 1.46.4`` with default scope in Maven. I'd suggest to ask the Flapdoodle guys when you're having trouble with Gradle. – Michael Jess Jun 10 '16 at 11:50
  • Thanks for responding. I've ended up creating a grails plugin to integrate with the flapdoodle embedded mongodb: https://github.com/grails-plugins/grails-embedded-mongodb – James Kleeh Jun 15 '16 at 15:17