Library to allow mocking of a Play-ReactiveMongo app's MongoDB back end.
The intention of this library is to facilitate the writing of tests which:
- Cover DAO- or Service-level logic around backend database calls
- Allow a level of component testing beyond simple unit tests
- Execute far faster than conventional "seeded database" tests
- Eliminate the potential issues around inconsistent and/or shared database state
Due to the nature of mocking method calls, it is essential that the version of this library matches exactly the version of [https://github.com/ReactiveMongo/Play-ReactiveMongo].
Bring in the library by adding the following to your Play project's build.sbt
.
- The release repository:
resolvers ++= Seq(
"Millhouse Bintray" at "http://dl.bintray.com/themillhousegroup/maven"
)
- The dependency itself:
libraryDependencies ++= Seq(
"com.themillhousegroup" %% "play2-reactivemongo-mocks" % "0.11.9_0.5.35"
)
The above version (the 0.5.x family) is for Play-ReactiveMongo 0.11.9 and Play 2.4. As Play 2.4 is for Scala 2.11, this library is correspondingly now only for Scala 2.11.
libraryDependencies ++= Seq(
"com.themillhousegroup" %% "play2-reactivemongo-mocks" % "0.11.11_0.7.42"
)
The 0.6.x family was required for Play 2.5.x support and moves the expected ReactiveMongo version to 0.11.11. The 0.7.x family fixes deprecation warnings in the underlying ReactiveMongo library.
The 0.4.x versions of this library incorrectly specified a very old version of the Specs2 library; this was reported in Issue 2 and resulted in the 0.5.x line. All 0.4.x versions should be considered deprecated; upgrading to 0.5.x is painless and it is completely backwards-compatible.
If you are using Play 2.3, you can still use the older published 0.3.x versions of this library, which target Play-ReactiveMongo 0.10.5.0.akka23, in both Scala 2.10 and Scala 2.11 flavours. Substitute the following dependency:
"com.themillhousegroup" %% "play2-reactivemongo-mocks" % "0.10.5.0.akka23_0.3.11"
Two modes of usage exist - the first will be more familiar to those who have used mocking libraries such as Mockito before. The second is a "higher-level" abstraction over the Mongo datastore, where the entire store (or at least, the part(s) being tested) are represented as a Map of Maps. If you are executing complex multi-Collection join-like queries, it may end up being a more readable solution.
Let's say you have a simple Controller
that uses ReactiveMongo just like in their sample tutorial:
class PersonController @Inject() (val reactiveMongoApi: ReactiveMongoApi)
extends Controller with MongoController with ReactiveMongoComponents {
def collection: JSONCollection = db.collection[JSONCollection]("persons")
def findByName(name: String) = Action.async {
val cursor: Cursor[JsObject] = collection.
find(Json.obj("name" -> name)).
sort(Json.obj("created" -> -1)).
cursor[JsObject]
val futurePersonsList: Future[List[JsObject]] = cursor.collect[List]()
val futurePersonsJsonArray: Future[JsArray] =
futurePersonsList.map { persons => Json.arr(persons) }
futurePersonsJsonArray.map { persons =>
Ok(persons)
}
}
}
Great. You can run this against a real Mongo instance and it works fine. Now let's add some test coverage.
Firstly, just mix in MongoMocks
into our standard Specs2 specification:
import org.specs2.mutable.Specification
import com.themillhousegroup.reactivemongo.mocks.MongoMocks
class PersonControllerSpec extends Specification with MongoMocks {
}
If you just want your mock persistence layer to always reply with meaningful results, use the mockedCollection(name)
method together with one or more of the givenMongo...
methods, like this:
class PersonControllerSpec extends Specification with MongoMocks {
val mockedPersons = mockedCollection("persons")(this.mockDB)
val people = List(
Json.obj("name" -> "Alice", "created" -> 123456789L),
Json.obj("name" -> "Bob", "created" -> 333333389L)
)
givenMongoCollectionFindAnyReturns(mockedPersons, people)
val testController = new PersonController
"some test of person finding" in {
}
}
Mix in MappedMongoMocking
and define mockData
- a Map[String, Set[JsObject]
i.e. mapping CollectionName to its contents. That's it! All of your Mongo find()
operations will now work against this data.
Standing on the shoulders of giants: