Documentation

§Testing with compile-time Dependency Injection

If you’re manually wiring up your application or using compile time dependency injection then you can directly use and customise your application components, or create a test variant specific to your test case. You can also modify and override filters, define routes, and specify configuration.

§BuiltInComponentsFromContext

BuiltInComponentsFromContext gives us an easy way to bootstrap your components. Given the context, this provides all required built in components: environment, configuration, applicationLifecycle, etc.

As described in compile time dependency injection, this is the most common way of wiring up the application manually.

When testing, we can use the real components which allows us to start the complete application for full functional testing, or we can create a test components which starts a subset of the application as required.

§WithApplicationComponents

Key to testing the components is the WithApplicationComponents trait. This sets up the application, server and context ready for testing. There are a number of sub-traits available to mixin depending on your testing strategy
* OneAppPerSuiteWithComponents
* OneAppPerTestWithComponents
* OneServerPerSuiteWithComponents
* OneServerPerTestWithComponents

It is recommend to familiarise yourself with the documentation of each trait in order to decide which best fits your needs.

§Defining the components inline

As discussed, the components can be defined in line within the test. To do this, simply override the components and complete the implementation of the BuiltInComponentsFromContext , providing the router.

override def components: BuiltInComponents = new BuiltInComponentsFromContext(context) with NoHttpFiltersComponents {

  import play.api.mvc.Results
  import play.api.routing.Router
  import play.api.routing.sird._

  lazy val router: Router = Router.from({
    case GET(p"/") => defaultActionBuilder {
      Results.Ok("success!")
    }
  })
  override lazy val configuration: Configuration = context.initialConfiguration ++ Configuration("foo" -> "bar", "ehcacheplugin" -> "disabled")
}

Above:
* We define the imports within the implementation to prevent conflicts between the sird and play.api.http packages when asserting verbs.
* We define a test router and implement the appropriate routes, in this case we match the root patch.
* We override the configuration to provide additional values to be used within the test, this is of course optional.

§Using existing components

If we want to use our existing application components, we can simply instantiate those within the test.

override def components: BuiltInComponents = new SomeAppComponents(context)

Additionally, it’s possible to override any definitions within the componentsat this stage, to provide additional configuration or mock a database for example.

override def components: BuiltInComponents = new SomeAppComponents(context) {
  override lazy val configuration: Configuration = context.initialConfiguration ++ Configuration("ehcacheplugin" -> "enabled")
}

§Complete Example

class ExampleComponentsSpec extends PlaySpec with OneAppPerSuiteWithComponents {

  // #scalacomponentstest-inlinecomponents
  override def components: BuiltInComponents = new BuiltInComponentsFromContext(context) with NoHttpFiltersComponents {

    import play.api.mvc.Results
    import play.api.routing.Router
    import play.api.routing.sird._

    lazy val router: Router = Router.from({
      case GET(p"/") => defaultActionBuilder {
        Results.Ok("success!")
      }
    })
    override lazy val configuration: Configuration = context.initialConfiguration ++ Configuration("foo" -> "bar", "ehcacheplugin" -> "disabled")
  }
  // #scalacomponentstest-inlinecomponents

  "The OneAppPerSuiteWithComponents trait" must {
    "provide an Application" in {
      import play.api.test.Helpers.{ GET, route }
      val Some(result): Option[Future[Result]] = route(app, FakeRequest(GET, "/"))
      Helpers.contentAsString(result) must be("success!")
    }
    "override the configuration" in {
      app.configuration.getOptional[String]("foo") mustBe Some("bar")
    }
  }
}

§Nested Specs

If you have many tests that can share the same Application, and you don’t want to put them all into one test class, you can place them into different Suite classes. These will be your nested suites. Create a master suite that extends the appropriate trait, for exampleOneAppPerSuiteWithComponents, and declares the nested Suites. Finally, annotate the nested suites with @DoNotDiscover and have them extend ConfiguredApp. Here’s an example:

class NestedExampleSpec extends Suites(
  new OneSpec,
  new TwoSpec,
  new RedSpec,
  new BlueSpec
) with OneAppPerSuiteWithComponents with TestSuite {

  override def components: BuiltInComponents = new BuiltInComponentsFromContext(context) with NoHttpFiltersComponents {

    import play.api.mvc.Results
    import play.api.routing.Router
    import play.api.routing.sird._

    lazy val router: Router = Router.from({
      case GET(p"/") => defaultActionBuilder {
        Results.Ok("success!")
      }
    })

    override lazy val configuration: Configuration = context.initialConfiguration ++ Configuration("ehcacheplugin" -> "disabled")
  }
}

// These are the nested suites
@DoNotDiscover class OneSpec extends PlaySpec with ConfiguredApp {
  "OneSpec" must {
    "make the Application available implicitly" in {
      def getConfig(key: String)(implicit app: Application) = app.configuration.getOptional[String](key)

      getConfig("ehcacheplugin") mustBe Some("disabled")
    }
  }

}

@DoNotDiscover class TwoSpec extends PlaySpec with ConfiguredApp

@DoNotDiscover class RedSpec extends PlaySpec with ConfiguredApp

@DoNotDiscover class BlueSpec extends PlaySpec with ConfiguredApp {

  "The NestedExampleSpec" must {
    "provide an Application" in {
      import play.api.test.Helpers.{ GET, route }
      val Some(result): Option[Future[Result]] = route(app, FakeRequest(GET, "/"))
      Helpers.contentAsString(result) must be("success!")
    }
  }
}

Next: Testing with databases


Found an error in this documentation? The source code for this page can be found here. After reading the documentation guidelines, please feel free to contribute a pull request.