Very simple Dependency Injection in Play applications
One of the first problem any developer will find when trying to develop a play application is how to unit test a controller. The official documentation covers integration testing (with the FakeApplication class) but remains silent when it comes to unit testing.
Luckily for us, since play 2.1 it is possible to write our routes in a way that lets us control the instantiation of the controllers opening the gate to dependency injection with "Managed Controller classes instatiation" (see here).
A route configured as GET / @controllers.Application.index()
will call our GlobalSettings object asking for an instance of controllers.Application
A very simple implementation of the DI pattern would be like this:
object Global extends config.DIGlobal {
}
The global object inherits from a class. This is so that it is easier to declare the type (we can use typeOf[DIGlobal]).
package config
import play.api.GlobalSettings
import controllers.Application
class DIGlobal extends DependencyInjectionGlobal[DIGlobal] {
//Application objects
val applicationController = new Application("0.0.1")
}
This class declares all the vals. As they support dependency injection, we do the wiring here by calling the constructors with the right arguments. This way, we have a typesafe way to wire the dependencies (the compiler will check that all the dependencies exist and are of the right type) and that we have no inconsistent objects in our system (and also avoid some setXX calls :) ).
package config
import scala.reflect.runtime.{ universe => ru }
import play.api.GlobalSettings
/*
* This trait allows GlobalSettings objects to use dynamic routing by using the method
* getDependencyOfType that iterates over the vals defined in the class and finds the
* first of the type requested.
* It also caches the results so that reflection is done only once per controller class
*/
abstract class DependencyInjectionGlobal[T <: DependencyInjectionGlobal[T] : ru.TypeTag] extends GlobalSettings {
private var dependencies = collection.mutable.Map[Class[_], Any]()
private val mirror = ru.runtimeMirror(this.getClass.getClassLoader)
def findValByClass[A](clazz: Class[A]): A = {
val member = ru.typeOf[T].members.find { v =>
v.isTerm && v.asTerm.typeSignature.equals(mirror.classSymbol(clazz).toType)
}
if (!member.isDefined) {
throw new IllegalStateException(s"There is no val of class ${clazz.getName} in class " + ru.typeOf[T])
}
mirror.reflect(this).reflectField(member.get.asTerm).get.asInstanceOf[A]
}
override def getControllerInstance[A](clazz: Class[A]) = {
dependencies.getOrElseUpdate(clazz, findValByClass(clazz)).asInstanceOf[A]
}
}
Finally, the class DependencyInjectionGlobal[T] implentes the getControllerInstance method for us by searching for vals of the right type in the subclass. It also caches the resolved dependencies as, being vals, they should not mutate and, thus, it makes no sense to do the reflection at every request.