Play framework: Dependency Injection for Unit Test
I- Problem:
When one has to write Unit Test for Play Framework apps, more than usual one will have to use FakeApplication to provide an Application context. For example when one needs to use a different database for testing purpose, or when one needs to provide a crypto secret for session, cookies signing and many many other case. This was because originally Play Framework was designed with global state objects, there is one global Scala object to provide your application with Database (DB), cache (Cache), messages (Messages) etc…
So when you want to use a different database for testing your model, you would write something like this:
val fakeApp: FakeApplication = FakeApplication( additionalConfiguration = inMemoryDatabase( options = Map("MODE" -> "MYSQL") ) )
and during the test:
"userDao" should { "be able to retrieve all records" in new withApplication(fakeApp) { ... }
Naturally, one would wonder why he has to setup a whole fake application just to replace one component for testing. He only needs to swap out a different database, yet he has to setup all the crypto, session, configuration, messages etc..
But not only counter-intuitive, this also have many other downsize:
- Performance: For each test, you will have to setup the resource pool for all kinds of resources. Even when you don’t need all of them in your test.
- Speed: Setting up resources take time, and the longer the code-compile-test cycle, the harder it is for you to convince your team (or even yourself!) to apply TDD.
- Resource conservation: Resources aren’t free, you don’t need to waste 10 connections to SQL server when you only need to provide a 10 characters secret to your app
Last but not least, relying on global state is bad for developing large, complex software. So global state and using FakeApplication need to go.
II- The Solution:
The Play developers also realised this, and starting from Play version 2.4, global state is being removed and Dependency Injection is now used to provide components to your app. With this one can easily provide all the dependencies needed in your Unit Test, not just your own objects but also Play components such as Database.
So for the test above, we can rewrite the class being tested like this:
class UserDAO @Inject() (db: Database) extends AbstractDao[UserRecord] { def getById(userId: Long): Try[UserRecord] = Try { db.withConnection { implicit connection => SQL(selectClause) ...
And the setup during the test:
val testDb = Database(....) val guice = new GuiceInjectorBuilder() .overrides(bind[Database].toInstance(testDb)) .build() val userDao = guice.instanceOf[UserDAO] ... "userDao" should { "be able to retrieve all records" in { userDao.getAll(...) } }
But that’s not all, you can also use Play Evolution to bring the test database to the newest state:
class mockDbSpec extends PlaySpecification with BeforeAll with AfterAll with Mockito with TestDBHelper { sequential def beforeAll() = Evolutions.applyEvolutions(testDb) def afterAll() = { Evolutions.cleanupEvolutions(testDb) testDb.shutdown() } }
By scaffolding the tests like this, you will be sure that the database will be in consistent state between the tests, so running individual test in isolation will be feasible.
Unfortunately, the migration to Dependency Injection is not completed yet, so in many cases you still have to fall back to using FakeApplication. One example is Cookie object which needs a Crypto secret for signing. Currently Play uses global Crypto object for this so you still need to use FakeApplication (and wasting 10 SQL connections!) when you only need to sign a cookie for testing your controller. You can see the implementation here
And the code snipper:
import play.api.libs.Crypto .... def encode(data: Map[String, String]): String = { val encoded = data.map { case (k, v) => URLEncoder.encode(k, "UTF-8") + "=" + URLEncoder.encode(v, "UTF-8") }.mkString("&") if (isSigned) Crypto.sign(encoded) + "-" + encoded else encoded }
III. Conclusion
With the changes in Play 2.4, one should avoid using FakeApplication as much as possible. Using Dependency Injection grants the ability maintain the relationship between components, making it much easier to write isolated unit test and control the complexity of your program.