Documentation

You are viewing the documentation for the 2.8.0 release in the 2.8.x series of releases. The latest stable release series is 3.0.x.

§Writing functional tests with ScalaTest

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. The ScalaTest + Play integration library builds on this testing support for ScalaTest.

You can access all of Play’s built-in test support and ScalaTest + Play with the following imports:

import org.scalatest._
import org.scalatestplus.play._
import play.api.http.MimeTypes
import play.api.test._

§Creating Application instances for testing

Play frequently requires a running Application as context. Providing an application to a test environment depends on how the application is built. 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 = new GuiceApplicationBuilder()
  .configure("some.configuration" -> "value")
  .build()

Rather than create an application with GuiceApplicationBuilder explicitly, a default application can be created by mixing in the GuiceFakeApplicationFactory trait.

If all or most tests in your test class need a Application, and they can all share the same instance of Application, mix in trait GuiceOneAppPerSuite with the GuiceFakeApplicationFactory trait. You can access the Application from the app field.

If you need to customize the Application, override fakeApplication() as shown in this example:

class ExampleSpec extends PlaySpec with GuiceOneAppPerSuite {

  // Override fakeApplication if you need a Application with other than
  // default parameters.
  override def fakeApplication(): Application = {
    GuiceApplicationBuilder().configure(Map("ehcacheplugin" -> "disabled")).build()
  }

  "The GuiceOneAppPerSuite trait" must {
    "provide an Application" in {
      app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
  }
}

You can also use GuiceOneAppPerSuite if you prefer not to mix in GuiceFakeApplicationFactory by hand.

If you need each test to get its own Application, instead of sharing the same one, use OneAppPerTest or GuiceOneAppPerTest instead:

class ExampleSpec extends PlaySpec with GuiceOneAppPerTest {

  // Override newAppForTest if you need an Application with other than
  // default parameters.
  override def newAppForTest(td: TestData): Application = {
    GuiceApplicationBuilder().configure(Map("ehcacheplugin" -> "disabled")).build()
  }

  "The OneAppPerTest trait" must {
    "provide a new Application for each test" in {
      app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
  }
}

The reason ScalaTest + Play provides both GuiceOneAppPerSuite and OneAppPerTest is to allow you to select the sharing strategy that makes your tests run fastest. If you want application state maintained between successive tests, you’ll need to use GuiceOneAppPerSuite. If each test needs a clean slate, however, you could either use OneAppPerTest or use GuiceOneAppPerSuite, but clear any state at the end of each test. Furthermore, if your test suite will run fastest if multiple test classes share the same application, you can define a master suite that mixes in GuiceOneAppPerSuite and nested suites that mix in ConfiguredApp, as shown in the example in the documentation for ConfiguredApp. You can use whichever strategy makes your test suite run the fastest.

§Testing with a server

Sometimes you want to test with the real HTTP stack. If all tests in your test class can reuse the same server instance, you can mix in OneServerPerSuite (which will also provide a new Application for the suite):

class ExampleSpec extends PlaySpec with GuiceOneServerPerSuite {

  // Override app if you need an Application with other than
  // default parameters.
  override def fakeApplication(): Application = {
    GuiceApplicationBuilder()
      .appRoutes(app => {
        case ("GET", "/") => app.injector.instanceOf(classOf[DefaultActionBuilder]) { Ok("ok") }
      })
      .build()
  }

  "test server logic" in {
    val wsClient              = app.injector.instanceOf[WSClient]
    val myPublicAddress       = s"localhost:$port"
    val testPaymentGatewayURL = s"http://$myPublicAddress"
    // 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(wsClient.url(testPaymentGatewayURL).addQueryStringParameters("callbackURL" -> callbackURL).get())

    response.status mustBe OK
  }
}

If all tests in your test class require separate server instances, use OneServerPerTest instead (which will also provide a new Application for the suite):

class ExampleSpec extends PlaySpec with GuiceOneServerPerTest {

  // Override newAppForTest or mixin GuiceFakeApplicationFactory and use fakeApplication() for an Application
  override def newAppForTest(testData: TestData): Application = {
    GuiceApplicationBuilder()
      .appRoutes(app => {
        case ("GET", "/") =>
          app.injector.instanceOf(classOf[DefaultActionBuilder]) {
            Ok("ok")
          }
      })
      .build()
  }

  "The OneServerPerTest trait" must {
    "test server logic" in {
      val wsClient              = app.injector.instanceOf[WSClient]
      val myPublicAddress       = s"localhost:$port"
      val testPaymentGatewayURL = s"http://$myPublicAddress"
      // 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(wsClient.url(testPaymentGatewayURL).addQueryStringParameters("callbackURL" -> callbackURL).get())

      response.status mustBe OK
    }
  }
}

The OneServerPerSuite and OneServerPerTest traits provide the port number on which the server is running as the port field. By default this is 19001, however you can change this either overriding port 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.

You can also customize the Application by overriding app, as demonstrated in the previous examples.

Lastly, if allowing multiple test classes to share the same server will give you better performance than either the OneServerPerSuite or OneServerPerTest approaches, you can define a master suite that mixes in OneServerPerSuite and nested suites that mix in ConfiguredServer, as shown in the example in the documentation for ConfiguredServer.

§Testing with a web browser

The ScalaTest + Play library builds on ScalaTest’s Selenium DSL to make it easy to test your Play applications from web browsers.

To run all tests in your test class using a same browser instance, mix OneBrowserPerSuite into your test class. You’ll also need to mix in a BrowserFactory trait that will provide a Selenium web driver: one of ChromeFactory, FirefoxFactory, HtmlUnitFactory, InternetExplorerFactory, SafariFactory.

In addition to mixing in a BrowserFactory, you will need to mix in a ServerProvider trait that provides a TestServer: one of OneServerPerSuite, OneServerPerTest, or ConfiguredServer.

For example, the following test class mixes in OneServerPerSuite and HtmUnitFactory:

class ExampleSpec extends PlaySpec with GuiceOneServerPerSuite with OneBrowserPerSuite with HtmlUnitFactory {

  // Override app if you need an Application with other than
  // default parameters.
  override def fakeApplication(): Application = {
    import play.api.http.MimeTypes._
    import play.api.mvc.Results._

    GuiceApplicationBuilder()
      .appRoutes(app => {
        case ("GET", "/testing") =>
          app.injector.instanceOf(classOf[DefaultActionBuilder]) {
            Ok("""
                 |<html>
                 | <head>
                 |   <title>Test Page</title>
                 |   <body>
                 |     <input type='button' name='b' value='Click Me' onclick='document.title="scalatest"' />
                 |   </body>
                 | </head>
                 |</html>
            """.stripMargin).as(HTML)
          }
      })
      .build()
  }

  "The OneBrowserPerTest trait" must {
    "provide a web driver" in {
      go to s"http://localhost:$port/testing"
      pageTitle mustBe "Test Page"
      click.on(find(name("b")).value)
      eventually { pageTitle mustBe "scalatest" }
    }
  }
}

If each of your tests requires a new browser instance, use OneBrowserPerTest instead. As with OneBrowserPerSuite, you’ll need to also mix in a ServerProvider and BrowserFactory:

class ExampleSpec extends PlaySpec with GuiceOneServerPerTest with OneBrowserPerTest with HtmlUnitFactory {

  // Override app if you need an Application with other than
  // default parameters.
  override def newAppForTest(testData: TestData): Application = {
    import play.api.http.MimeTypes._
    import play.api.mvc.Results._

    GuiceApplicationBuilder()
      .appRoutes(app => {
        case ("GET", "/testing") =>
          app.injector.instanceOf(classOf[DefaultActionBuilder]) {
            Ok("""
                 |<html>
                 | <head>
                 |   <title>Test Page</title>
                 |   <body>
                 |     <input type='button' name='b' value='Click Me' onclick='document.title="scalatest"' />
                 |   </body>
                 | </head>
                 |</html>
            """.stripMargin).as(HTML)
          }
      })
      .build()
  }

  "The OneBrowserPerTest trait" must {
    "provide a web driver" in {
      go to (s"http://localhost:$port/testing")
      pageTitle mustBe "Test Page"
      click.on(find(name("b")).value)
      eventually { pageTitle mustBe "scalatest" }
    }
  }
}

If you need multiple test classes to share the same browser instance, mix OneBrowserPerSuite into a master suite and ConfiguredBrowser into multiple nested suites. The nested suites will all share the same web browser. For an example, see the documentation for trait ConfiguredBrowser.

§Running the same tests in multiple browsers

If you want to run tests in multiple web browsers, to ensure your application works correctly in all the browsers you support, you can use traits AllBrowsersPerSuite or AllBrowsersPerTest. Both of these traits declare a browsers field of type IndexedSeq[BrowserInfo] and an abstract sharedTests method that takes a BrowserInfo. The browsers field indicates which browsers you want your tests to run in. The default is Chrome, Firefox, Internet Explorer, HtmlUnit, and Safari. You can override browsers if the default does not fit your needs. You place tests you want to run in multiple browsers in the sharedTests method, placing the name of the browser at the end of each test name. (The browser name is available from the BrowserInfo passed into sharedTests.) Here is an example that uses AllBrowsersPerSuite:

class ExampleSpec extends PlaySpec with GuiceOneServerPerSuite with AllBrowsersPerSuite {

  // Override app if you need an Application with other than
  // default parameters.
  override def fakeApplication(): Application = {
    import play.api.http.MimeTypes._
    import play.api.mvc.Results._

    GuiceApplicationBuilder()
      .appRoutes(app => {
        case ("GET", "/testing") =>
          app.injector.instanceOf(classOf[DefaultActionBuilder]) {
            Ok("""
                 |<html>
                 | <head>
                 |   <title>Test Page</title>
                 |   <body>
                 |     <input type='button' name='b' value='Click Me' onclick='document.title="scalatest"' />
                 |   </body>
                 | </head>
                 |</html>
            """.stripMargin).as(HTML)
          }
      })
      .build()
  }

  def sharedTests(browser: BrowserInfo) = {
    "The AllBrowsersPerSuite trait" must {
      "provide a web driver " + browser.name in {
        go to s"http://localhost:$port/testing"
        pageTitle mustBe "Test Page"
        click.on(find(name("b")).value)
        eventually { pageTitle mustBe "scalatest" }
      }
    }
  }
}

All tests declared by sharedTests will be run with all browsers mentioned in the browsers field, so long as they are available on the host system. Tests for any browser that is not available on the host system will be canceled automatically.

Note: You need to append the browser.name manually to the test name to ensure each test in the suite has a unique name (which is required by ScalaTest). If you leave that off, you’ll get a duplicate-test-name error when you run your tests.

AllBrowsersPerSuite will create a single instance of each type of browser and use that for all the tests declared in sharedTests. If you want each test to have its own, brand new browser instance, use AllBrowsersPerTest instead:

class ExampleSpec extends PlaySpec with GuiceOneServerPerSuite with AllBrowsersPerTest {

  // Override app if you need an Application with other than
  // default parameters.
  override def fakeApplication(): Application = {
    import play.api.http.MimeTypes._
    import play.api.mvc.Results._

    GuiceApplicationBuilder()
      .appRoutes(app => {
        case ("GET", "/testing") =>
          app.injector.instanceOf(classOf[DefaultActionBuilder]) {
            Ok("""
                 |<html>
                 | <head>
                 |   <title>Test Page</title>
                 |   <body>
                 |     <input type='button' name='b' value='Click Me' onclick='document.title="scalatest"' />
                 |   </body>
                 | </head>
                 |</html>
            """.stripMargin).as(HTML)
          }
      })
      .build()
  }

  def sharedTests(browser: BrowserInfo) = {
    "The AllBrowsersPerTest trait" must {
      "provide a web driver" + browser.name in {
        go to (s"http://localhost:$port/testing")
        pageTitle mustBe "Test Page"
        click.on(find(name("b")).value)
        eventually { pageTitle mustBe "scalatest" }
      }
    }
  }
}

Although both AllBrowsersPerSuite and AllBrowsersPerTest will cancel tests for unavailable browser types, the tests will show up as canceled in the output. To can clean up the output, you can exclude web browsers that will never be available by overriding browsers, as shown in this example:

class ExampleOverrideBrowsersSpec extends PlaySpec with GuiceOneServerPerSuite with AllBrowsersPerSuite {

  override lazy val browsers =
    Vector(FirefoxInfo(firefoxProfile), ChromeInfo())

  // Override app if you need an Application with other than
  // default parameters.
  override def fakeApplication(): Application = {
    import play.api.http.MimeTypes._
    import play.api.mvc.Results._

    GuiceApplicationBuilder()
      .appRoutes(app => {
        case ("GET", "/testing") =>
          app.injector.instanceOf(classOf[DefaultActionBuilder]) {
            Ok("""
                 |<html>
                 | <head>
                 |   <title>Test Page</title>
                 |   <body>
                 |     <input type='button' name='b' value='Click Me' onclick='document.title="scalatest"' />
                 |   </body>
                 | </head>
                 |</html>
            """.stripMargin).as(HTML)
          }
      })
      .build()
  }

  def sharedTests(browser: BrowserInfo) = {
    "The AllBrowsersPerSuite trait" must {
      "provide a web driver" + browser.name in {
        go to (s"http://localhost:$port/testing")
        pageTitle mustBe "Test Page"
        click.on(find(name("b")).value)
        eventually { pageTitle mustBe "scalatest" }
      }
    }
  }
}

The previous test class will only attempt to run the shared tests with Firefox and Chrome (and cancel tests automatically if a browser is not available).

§PlaySpec

PlaySpec provides a convenience “super Suite” ScalaTest base class for Play tests. You get WordSpec, MustMatchers, OptionValues, and WsScalaTestClient automatically by extending PlaySpec:

class ExampleSpec extends PlaySpec with GuiceOneServerPerSuite with ScalaFutures with IntegrationPatience {

  // Override app if you need an Application with other than
  // default parameters.
  override def fakeApplication(): Application = {

    import play.api.http.MimeTypes._
    import play.api.mvc.Results._

    GuiceApplicationBuilder()
      .appRoutes(app => {
        case ("GET", "/testing") =>
          app.injector.instanceOf(classOf[DefaultActionBuilder]) {
            Ok("""
                 |<html>
                 | <head>
                 |   <title>Test Page</title>
                 |   <body>
                 |     <input type='button' name='b' value='Click Me' onclick='document.title="scalatest"' />
                 |   </body>
                 | </head>
                 |</html>""".stripMargin).as(HTML)
          }
      })
      .build()
  }

  "WsScalaTestClient's" must {

    "wsUrl works correctly" in {
      implicit val ws: WSClient = app.injector.instanceOf(classOf[WSClient])
      val futureResult          = wsUrl("/testing").get
      val body                  = futureResult.futureValue.body
      val expectedBody =
        """
          |<html>
          | <head>
          |   <title>Test Page</title>
          |   <body>
          |     <input type='button' name='b' value='Click Me' onclick='document.title="scalatest"' />
          |   </body>
          | </head>
          |</html>""".stripMargin
      assert(body == expectedBody)
    }

    "wsCall works correctly" in {
      implicit val ws: WSClient = app.injector.instanceOf(classOf[WSClient])
      val futureResult          = wsCall(Call("get", "/testing")).get
      val body                  = futureResult.futureValue.body
      val expectedBody =
        """
          |<html>
          | <head>
          |   <title>Test Page</title>
          |   <body>
          |     <input type='button' name='b' value='Click Me' onclick='document.title="scalatest"' />
          |   </body>
          | </head>
          |</html>""".stripMargin
      assert(body == expectedBody)
    }
  }
}

You can mix any of the previously mentioned traits into PlaySpec.

§When different tests need different fixtures

In all the test classes shown in previous examples, all or most tests in the test class required the same fixtures. While this is common, it is not always the case. If different tests in the same test class need different fixtures, mix in trait MixedFixtures. Then give each individual test the fixture it needs using one of these no-arg functions: App, Server, Chrome, Firefox, HtmlUnit, InternetExplorer, or Safari.

You cannot mix MixedFixtures into PlaySpec because MixedFixtures requires a ScalaTest fixture.Suite and PlaySpec is just a regular Suite. If you want a convenient base class for mixed fixtures, extend MixedPlaySpec instead. Here’s an example:

// MixedPlaySpec already mixes in MixedFixtures
class ExampleSpec extends MixedPlaySpec {

  // Some helper methods
  def buildApp[A](elems: (String, String)*): Application = {
    import play.api.http.MimeTypes._
    import play.api.mvc.Results._

    GuiceApplicationBuilder()
      .appRoutes(app => {
        case ("GET", "/testing") =>
          app.injector.instanceOf(classOf[DefaultActionBuilder]) {
            Ok("""
                 |<html>
                 | <head>
                 |   <title>Test Page</title>
                 |   <body>
                 |     <input type='button' name='b' value='Click Me' onclick='document.title="scalatest"' />
                 |   </body>
                 | </head>
                 |</html>
            """.stripMargin).as(HTML)
          }
      })
      .configure(Map(elems: _*))
      .build()
  }

  def getConfig(key: String)(implicit app: Application): Option[String] = app.configuration.getOptional[String](key)

  // If a test just needs an Application, use "new App":
  "The App function" must {
    "provide an Application" in new App(buildApp("ehcacheplugin" -> "disabled")) {
      app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new App(buildApp("ehcacheplugin" -> "disabled")) {
      getConfig("ehcacheplugin") mustBe Some("disabled")
    }
  }

  // If a test needs an Application and running TestServer, use "new Server":
  "The Server function" must {
    "provide an Application" in new Server(buildApp("ehcacheplugin" -> "disabled")) {
      app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new Server(buildApp("ehcacheplugin" -> "disabled")) {
      getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new Server {
      import java.net._
      val url                    = new URL("http://localhost:" + port + "/boom")
      val con: HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection]
      try con.getResponseCode mustBe 404
      finally con.disconnect()
    }
  }

  // If a test needs an Application, running TestServer, and Selenium
  // HtmlUnit driver use "new HtmlUnit":
  "The HtmlUnit function" must {
    "provide an Application" in new HtmlUnit(buildApp("ehcacheplugin" -> "disabled")) {
      app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new HtmlUnit(buildApp("ehcacheplugin" -> "disabled")) {
      getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new HtmlUnit {
      import java.net._
      val url                    = new URL("http://localhost:" + port + "/boom")
      val con: HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection]
      try con.getResponseCode mustBe 404
      finally con.disconnect()
    }
    "provide a web driver" in new HtmlUnit(buildApp()) {
      go to ("http://localhost:" + port + "/testing")
      pageTitle mustBe "Test Page"
      click.on(find(name("b")).value)
      eventually { pageTitle mustBe "scalatest" }
    }
  }

  // If a test needs an Application, running TestServer, and Selenium
  // Firefox driver use "new Firefox":
  "The Firefox function" must {
    "provide an application" in new Firefox(buildApp("ehcacheplugin" -> "disabled")) {
      app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new Firefox(buildApp("ehcacheplugin" -> "disabled")) {
      getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new Firefox {
      import java.net._
      val url                    = new URL("http://localhost:" + port + "/boom")
      val con: HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection]
      try con.getResponseCode mustBe 404
      finally con.disconnect()
    }
    "provide a web driver" in new Firefox(buildApp()) {
      go to ("http://localhost:" + port + "/testing")
      pageTitle mustBe "Test Page"
      click.on(find(name("b")).value)
      eventually { pageTitle mustBe "scalatest" }
    }
  }

  // If a test needs an Application, running TestServer, and Selenium
  // Safari driver use "new Safari":
  "The Safari function" must {
    "provide an Application" in new Safari(buildApp("ehcacheplugin" -> "disabled")) {
      app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new Safari(buildApp("ehcacheplugin" -> "disabled")) {
      getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new Safari {
      import java.net._
      val url                    = new URL("http://localhost:" + port + "/boom")
      val con: HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection]
      try con.getResponseCode mustBe 404
      finally con.disconnect()
    }
    "provide a web driver" in new Safari(buildApp()) {
      go to ("http://localhost:" + port + "/testing")
      pageTitle mustBe "Test Page"
      click.on(find(name("b")).value)
      eventually { pageTitle mustBe "scalatest" }
    }
  }

  // If a test needs an Application, running TestServer, and Selenium
  // Chrome driver use "new Chrome":
  "The Chrome function" must {
    "provide an Application" in new Chrome(buildApp("ehcacheplugin" -> "disabled")) {
      app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new Chrome(buildApp("ehcacheplugin" -> "disabled")) {
      getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new Chrome {
      import java.net._
      val url                    = new URL("http://localhost:" + port + "/boom")
      val con: HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection]
      try con.getResponseCode mustBe 404
      finally con.disconnect()
    }
    "provide a web driver" in new Chrome(buildApp()) {
      go to ("http://localhost:" + port + "/testing")
      pageTitle mustBe "Test Page"
      click.on(find(name("b")).value)
      eventually { pageTitle mustBe "scalatest" }
    }
  }

  // If a test needs an Application, running TestServer, and Selenium
  // InternetExplorer driver use "new InternetExplorer":
  "The InternetExplorer function" must {
    "provide an Application" in new InternetExplorer(buildApp("ehcacheplugin" -> "disabled")) {
      app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new InternetExplorer(buildApp("ehcacheplugin" -> "disabled")) {
      getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new InternetExplorer {
      import java.net._
      val url                    = new URL("http://localhost:" + port + "/boom")
      val con: HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection]
      try con.getResponseCode mustBe 404
      finally con.disconnect()
    }
    "provide a web driver" in new InternetExplorer(buildApp()) {
      go to ("http://localhost:" + port + "/testing")
      pageTitle mustBe "Test Page"
      click.on(find(name("b")).value)
      eventually { pageTitle mustBe "scalatest" }
    }
  }

  // If a test does not need any special fixtures, just
  // write "in { () => ..."
  "Any old thing" must {
    "be doable without much boilerplate" in { () =>
      1 + 1 mustEqual 2
    }
  }
}

§Testing a 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 App {
  val html = views.html.index("Coco")

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

§Testing a controller

You can call any Action code by providing a FakeRequest:

import scala.concurrent.Future

import org.scalatestplus.play._

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

class ExampleControllerSpec extends PlaySpec with Results {

  "Example Page#index" should {
    "should be valid" in {
      val controller             = new ExampleController(Helpers.stubControllerComponents())
      val result: Future[Result] = controller.index().apply(FakeRequest())
      val bodyText: String       = contentAsString(result)
      bodyText mustBe "ok"
    }
  }
}

§Testing the router

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

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

  status(result) mustEqual OK
  contentType(result) mustEqual Some("text/html")
  contentAsString(result) must include("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 Helpers#inMemoryDatabase.

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

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

  macintosh.name mustEqual "Macintosh"
  macintosh.introduced.value mustEqual "1984-01-24"
}

§Testing WS calls

If you are calling a web service, you can use WSTestClient. There are two calls available, wsCall and wsUrl that will take a Call or a string, respectively. Note that they expect to be called in the context of a running application.

wsCall(controllers.routes.Application.index()).get()
wsUrl("http://localhost:9000").get()

Next: Testing with specs2