Skip to content

Latest commit

 

History

History
131 lines (101 loc) · 4.05 KB

File metadata and controls

131 lines (101 loc) · 4.05 KB

Play Framework with Compile Time DI Tests

This is an example of Play 2.5.x using the Scala API with manually wired compile time dependency injection.

The application loader here is MyApplicationLoader which uses MyComponents to wire together an injector. In testing, there are some places where a WSClient has to be used, and so some additional components have to be added in.

To do this, the injector has to be implemented specifying all of the built in components, plus the WS API, which is made available through AhcWSComponents:

import play.api._
import play.api.i18n._
import play.api.inject._
import play.api.libs.ws.ahc.AhcWSComponents
import play.api.routing.Router
import router.Routes

class MyApplicationLoader extends ApplicationLoader {
  def load(context: ApplicationLoader.Context) = {
    LoggerConfigurator(context.environment.classLoader).foreach {
      _.configure(context.environment)
    }
    new MyComponents(context).application
  }
}

class MyComponents(context: ApplicationLoader.Context) 
  extends BuiltInComponentsFromContext(context)
  with I18nComponents
  with AhcWSComponents {

  override lazy val injector =  {
    new SimpleInjector(NewInstanceInjector) +
      router +
      cookieSigner +
      csrfTokenSigner +
      httpConfiguration +
      tempFileCreator +
      global +
      crypto +
      wsApi +
      messagesApi
  }

  lazy val router: Router = new Routes(httpErrorHandler, homeController, assets)

  lazy val homeController = new controllers.HomeController()
  lazy val assets = new controllers.Assets(httpErrorHandler)
}

Now that MyComponents has the AhcWSComponents trait, it can use components.wsClient anywhere.

The ScalaTest suite mixins such as OneAppPerSuite use GuiceApplicationLoader for all the implicit Application set up, so to use dependency injection, the trait must be extended to use the components using types:

trait OneServerPerSuiteWithComponents[T <: BuiltInComponents]
  extends OneServerPerSuite
    with WithApplicationComponents[T] {
  this: Suite =>

  override implicit lazy val app: Application = newApplication
}

where WithApplicationComponents is defined as:

trait WithApplicationComponents[T <: BuiltInComponents] {
  private var _components: T = _

  // accessed to get the components in tests
  final def components: T = _components

  // overridden by subclasses
  def createComponents(context: Context): T

  // creates a new application and sets the components
  def newApplication: Application = {
    _components = createComponents(context)
    _components.application
  }

  def context: ApplicationLoader.Context = {
    val classLoader = ApplicationLoader.getClass.getClassLoader
    val env = new Environment(new java.io.File("."), classLoader, Mode.Test)
    ApplicationLoader.createContext(env)
  }
}

Then, depending on your components, you can set up a subtype of OneServerPerSuiteWithComponents using MyComponents:

trait OneServerPerSuiteWithMyComponents
  extends OneServerPerSuiteWithComponents[MyComponents] {
  this: Suite =>

  override def createComponents(context: Context): MyComponents = new MyComponents(context)
}

Once the OneServerPerSuiteWithMyComponents is defined, you must call out to the app lazy val, after which you can access the components field to get at the wsClient:

class ServerSpec extends PlaySpec
  with OneServerPerSuiteWithMyComponents
  with Results
  with ScalaFutures {

  "Server query should" should {

    "work" in {
      implicit val ec = app.materializer.executionContext
      val wsClient = components.wsClient

      whenReady(wsUrl("/")(portNumber, wsClient).get) { response =>
        response.status mustBe OK
      }
    }

  }
}

Further Documentation