I’m using Play Framework with MongoDB in my side project. Although Play is not by default ready to use document database it is really easy to switch from default SQL centric configuration. But one thing was a little bit tricky for me – database test configuration. I couldn’t find any recipe that would give me the solution so I came up with my own, using ScalaTest.
Preparing libraries
By default in Play Framework SQL database is configured. To use document database you need to do some additional things. In my case I use MongoDB. To connect your application to it I recommend ReactiveMongo driver. You do not have to do much to configure it in your Play application. All you need to do is to use Play-ReactiveMongo plugin. There is another default configuration that I’ve changed. By default Play Framework uses specs2 as testing library. I personally prefer to use ScalaTest. To switch test libraries you just need to add it to dependencies as you would add other libraries. A little more details can be found here.
Database configuration for tests
So now we have Play app with MongoDB and ScalaTest. We can already create and run unit tests but to write some database tests we still need to do some work. If we used SQL database we wouldn’t need to do much – Play would run fake, in memory database for us. But there is no (yet) fully implemented version of such fake database for MongoDB. There is e.g. fongo but, as far as I know, it is not a one to one implementation of original database’s features. In my case I assumed that during development I will still need a MongoDB instance on my computer so it won’t be a big problem to use this instance for tests. Of course running application and test on exactly the same database is, because of the obvious reasons, not the best idea. In this case I need to have a test configuration. Such configuration will help configuring CI server too.
This is my configuration of ReactiveMongo plugin for development.
1 2 |
mongodb.servers = ["localhost:27017"] mongodb.db = "play-app" |
In test resources I’ve created test.conf file. All keys from the application configuration can be overwritten there but I want to focus on the database.
3 4 |
mongodb.db="play-test" mongodb.uri=${?TEST_DB_URI} |
The first line is changing used database. Our application’s data is safe during tests now. The second line gives us the opportunity to change MongoDB instance entirely. If we have environment variable named TEST_DB_URI its value will be passed as key’s value. If this variable contains URI for database it will overwrite previously configured properties. On my CI server I have such variable created, pointing to some local database instance. If no such variable is found, key will be dropped like it never existed.
Loading test configuration
We have a test configuration ready. Now we need to load it during tests. We can do this two ways: “manually” (by passing localisation of a file in Java options, which is described here) or by loading file in Scala code. I prefer the second option. Loading configuration that way gives us a possibility to validate it. Exception will be thrown if we forget to overwrite crucial keys. TestConfiguration.scala is a trait that implements this functionality. Here are two important fragments.
19 20 |
val config: Config = ConfigFactory.parseURL(configFile) .resolve(ConfigResolveOptions.defaults()) |
30 31 32 |
if (!databaseConfigurationOverwrittenForTests) { throw new IllegalStateException("Configuration of test mongodb is missing") } |
Preparing test environment
So now we have our configuration read from file. Now we need to pass it to our fake application. To have a generic test I’ve created DatabaseSpec.scala. That trait extends FunSpec trait from ScalaTest with database functionality. You can extend any other ‘Spec’ trait available there, if you prefer. You can find three important parts in there.
21 |
implicit val fakeApp = FakeApplication(additionalConfiguration = configuration) |
Here we pass our test configuration. Created fake Play application will be started before every suite.
55 56 57 58 59 60 61 62 |
private def tryToInitDatabase { try { clearDatabase } catch { case e: Throwable => throw new IllegalStateException( "There is a problem with test environment!", e) } } |
Above we try to clear test database before test suite. If there is an error it probably means that our database is not running. Detail information will be passed as a cause.
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
protected def clearDatabase() { waitFor { ReactiveMongoPlugin.db.collectionNames.map { collectionNames => { waitFor { Future.sequence(collectionNames .filterNot(_.startsWith(SystemCollectionPrefix)).map { collectionName => ReactiveMongoPlugin.db.command(new Drop(collectionName)) }) } } } } } |
This code is responsible for clearing our database after each test. It will find all collections, filter out all system ones and drop the rest. What is very important, we need to block those queries. If they were non-blocking, next tests could run on some not yet deleted collections and we would have random test fails.
Usage of database functionality
With trait like this we can now create database tests with ease.
1 2 3 4 5 |
class RepositoryTestDB extends DatabaseSpec { describe("Repository") { it("should save entity")(pending) } } |
All technical things like starting Play application or clearing database after each test will be done without single, additional line. We can just focus on writing test and enjoy database access.
Summary
Such configuration is quite simple and clear but if you know better ways of writing database tests for MongoDB in Play application using ScalaTest please let me know. I’ll definitely check them out. If you like my solution or maybe find some things to fix just drop a comment.
Here are complete files. Feel free to use them as you like. Each file is distributed “as is”, without any warranties.
You can also give a try to Acolyte for ReactiveMongo, which allows to configured isolated instances of MongoDriver for each test case, returning expected query/write results according Mongo command executed by code to be tested, to be able to check this persistence code send proper command through ReactiveMongo, to validate this persistence code handles properly Mongo result. See http://acolyte.eu.org/reactive-mongo.html