Documentation

§Writing functional tests with specs2

Play provides a number of classes and convenience methods that assist with functional testing. Most of these can be found either in the play.api.test package or in the Helpers object.

You can add these methods and classes by importing the following:

import play.api.test._
import play.api.test.Helpers._

§Creating Application instances for testing

Play frequently requires a running Application as context. If you’re using the default Guice dependency injection, you can use the GuiceApplicationBuilder class which can be configured with different configuration, routes, or even additional modules.

val application: Application = GuiceApplicationBuilder().build()

§WithApplication

To pass in an application to an example, use WithApplication. An explicit Application can be passed in, but a default application (created from the default GuiceApplicationBuilder) is provided for convenience.

Because WithApplication is a built in Around block, you can override it to provide your own data population:

abstract class WithDbData extends WithApplication {
  override def around[T: AsResult](t: => T): Result = super.around {
    setupData()
    t
  }

  def setupData() {
    // setup data
  }
}

"Computer model" should {

  "be retrieved by id" in new WithDbData {
    // your test code
  }
  "be retrieved by email" in new WithDbData {
    // your test code
  }
}

§WithServer

Sometimes you want to test the real HTTP stack from within your test, in which case you can start a test server using WithServer:

"test server logic" in new WithServer(app = applicationWithBrowser, port = testPort) {
  // The test payment gateway requires a callback to this server before it returns a result...
  val callbackURL = s"http://$myPublicAddress/callback"

  // await is from play.api.test.FutureAwaits
  val response = await(WS.url(testPaymentGatewayURL).withQueryString("callbackURL" -> callbackURL).get())

  response.status must equalTo(OK)
}

The port value contains the port number the server is running on. By default this is 19001, however you can change this either by passing the port into WithServer, or by setting the system property testserver.port. This can be useful for integrating with continuous integration servers, so that ports can be dynamically reserved for each build.

An application can also be passed to the test server, which is useful for setting up custom routes and testing WS calls:

val appWithRoutes = GuiceApplicationBuilder().router(Router.from {
  case GET(p"/") => Action {
    Ok("ok")
  }
}).build()

"test WS logic" in new WithServer(app = appWithRoutes, port = 3333) {
  await(WS.url("http://localhost:3333").get()).status must equalTo(OK)
}

§WithBrowser

If you want to test your application using a browser, you can use Selenium WebDriver. Play will start the WebDriver for you, and wrap it in the convenient API provided by FluentLenium using WithBrowser. Like WithServer, you can change the port, Application, and you can also select the web browser to use:

def applicationWithBrowser = new GuiceApplicationBuilder().router(Router.from {
  case GET(p"/") =>
    Action {
      Ok(
        """
          |<html>
          |<body>
          |  <div id="title">Hello Guest</div>
          |  <a href="/login">click me</a>
          |</body>
          |</html>
        """.stripMargin) as "text/html"
    }
  case GET(p"/login") =>
    Action {
      Ok(
        """
          |<html>
          |<body>
          |  <div id="title">Hello Coco</div>
          |</body>
          |</html>
        """.stripMargin) as "text/html"
    }
}).build()

"run in a browser" in new WithBrowser(webDriver = WebDriverFactory(HTMLUNIT), app = applicationWithBrowser) {
  browser.goTo("/")

  // Check the page
  browser.$("#title").getTexts.get(0) must equalTo("Hello Guest")

  browser.$("a").click()

  browser.url must equalTo("/login")
  browser.$("#title").getTexts.get(0) must equalTo("Hello Coco")
}

§PlaySpecification

PlaySpecification is an extension of Specification that excludes some of the mixins provided in the default specs2 specification that clash with Play helpers methods. It also mixes in the Play test helpers and types for convenience.

object ExamplePlaySpecificationSpec extends PlaySpecification {
  "The specification" should {

    "have access to HeaderNames" in {
      USER_AGENT must be_===("User-Agent")
    }

    "have access to Status" in {
      OK must be_===(200)
    }
  }
}

§Testing a view template

Since a template is a standard Scala function, you can execute it from your test, and check the result:

"render index template" in new WithApplication {
  val html = views.html.index("Coco")

  contentAsString(html) must contain("Hello Coco")
}

§Testing a controller

You can call any Action code by providing a FakeRequest:

"respond to the index Action" in {
  val result = controllers.Application.index()(FakeRequest())

  status(result) must equalTo(OK)
  contentType(result) must beSome("text/plain")
  contentAsString(result) must contain("Hello Bob")
}

Technically, you don’t need WithApplication here, although it wouldn’t hurt anything to have it.

§Testing the router

Instead of calling the Action yourself, you can let the Router do it:

"respond to the index Action" in new WithApplication(applicationWithRouter) {
val Some(result) = route(app, FakeRequest(GET, "/Bob"))

  status(result) must equalTo(OK)
  contentType(result) must beSome("text/html")
  charset(result) must beSome("utf-8")
  contentAsString(result) must contain("Hello Bob")
}

§Testing a model

If you are using an SQL database, you can replace the database connection with an in-memory instance of an H2 database using inMemoryDatabase.

def appWithMemoryDatabase = new GuiceApplicationBuilder().configure(inMemoryDatabase("test")).build()
"run an application" in new WithApplication(appWithMemoryDatabase) {

  val Some(macintosh) = Computer.findById(21)

  macintosh.name must equalTo("Macintosh")
  macintosh.introduced must beSome.which(_ must beEqualTo("1984-01-24"))
}

Next: Testing with Guice


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.