文档

§使用 ScalaTest 编写功能测试

Play 提供了许多类和便利方法来帮助进行功能测试。大多数这些方法可以在 play.api.test 包或 Helpers 对象中找到。ScalaTest + Play 集成库基于此测试支持为 ScalaTest 提供支持。

您可以使用以下导入来访问 Play 的所有内置测试支持和ScalaTest + Play

import org.scalatest._
import org.scalatest.matchers.must.Matchers
import org.scalatest.wordspec.FixtureAnyWordSpec
import org.scalatestplus.play._
import play.api.http.MimeTypes
import play.api.test._
import play.api.test.Helpers._

§创建用于测试的 Application 实例

Play 经常需要一个正在运行的 Application 作为上下文。为测试环境提供应用程序取决于应用程序的构建方式。如果您使用默认的 Guice 依赖注入,您可以使用 GuiceApplicationBuilder 类,该类可以使用不同的配置、路由甚至额外的模块进行配置。

val application: Application = new GuiceApplicationBuilder()
  .configure("some.configuration" -> "value")
  .build()

与其显式地使用 GuiceApplicationBuilder 创建应用程序,不如通过混合 GuiceFakeApplicationFactory 特性来创建默认应用程序。

如果您的测试类中的所有或大多数测试都需要一个 Application,并且它们都可以共享同一个 Application 实例,请将 GuiceOneAppPerSuite 特性与 GuiceFakeApplicationFactory 特性混合。您可以从 app 字段访问 Application

如果您需要自定义 Application,请覆盖 fakeApplication(),如本示例所示

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")
    }
  }
}

如果您不想手动混合 GuiceFakeApplicationFactory,也可以使用 GuiceOneAppPerSuite

如果您需要每个测试都获得自己的 Application,而不是共享同一个 Application,请使用 OneAppPerTestGuiceOneAppPerTest 代替。

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")
    }
  }
}

ScalaTest + Play 提供 GuiceOneAppPerSuiteOneAppPerTest 的原因是为了让你选择最快的测试运行共享策略。如果你希望在连续测试之间维护应用程序状态,你需要使用 GuiceOneAppPerSuite。但是,如果每个测试都需要一个干净的环境,你可以使用 OneAppPerTest 或使用 GuiceOneAppPerSuite,但在每次测试结束时清除任何状态。此外,如果你的测试套件在多个测试类共享同一个应用程序时运行速度最快,你可以定义一个主套件,它混合了 GuiceOneAppPerSuite 和混合了 ConfiguredApp 的嵌套套件,如 ConfiguredApp 文档 中的示例所示。你可以使用任何使你的测试套件运行速度最快的策略。

§使用服务器进行测试

有时你希望使用真实的 HTTP 堆栈进行测试。如果你的测试类中的所有测试都可以重用同一个服务器实例,你可以混合使用 OneServerPerSuite(它也会为套件提供一个新的 Application

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
  }
}

如果你的测试类中的所有测试都需要单独的服务器实例,请改用 OneServerPerTest(它也会为套件提供一个新的 Application

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
    }
  }
}

OneServerPerSuiteOneServerPerTest 特性提供服务器运行的端口号作为 port 字段。默认情况下,这是一个随机端口,但是你可以通过覆盖 port 或设置系统属性 testserver.port 来更改它。这对于与持续集成服务器集成很有用,这样就可以为每个构建动态地保留端口。

你也可以通过覆盖 app 来自定义 Application,如前面的示例所示。

最后,如果允许多个测试类共享同一个服务器比使用 `OneServerPerSuite` 或 `OneServerPerTest` 方法提供更好的性能,您可以定义一个主套件,将 `OneServerPerSuite` 与嵌套套件混合,嵌套套件将 `ConfiguredServer` 混合在一起,如 `ConfiguredServer` 文档中的示例所示。

§使用 Web 浏览器进行测试

ScalaTest + Play 库基于 ScalaTest 的 Selenium DSL,可以轻松地从 Web 浏览器测试您的 Play 应用程序。

要使用同一个浏览器实例运行测试类中的所有测试,请将 `OneBrowserPerSuite` 混合到您的测试类中。您还需要混合一个 `BrowserFactory` 特征,该特征将提供一个 Selenium Web 驱动程序:`ChromeFactory`、`FirefoxFactory`、`HtmlUnitFactory`、`InternetExplorerFactory`、`SafariFactory` 之一。

除了混合 `BrowserFactory` 之外,您还需要混合一个 `ServerProvider` 特征,该特征提供一个 `TestServer`:`OneServerPerSuite`、`OneServerPerTest` 或 `ConfiguredServer` 之一。

例如,以下测试类混合了 `OneServerPerSuite` 和 `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"https://127.0.0.1:$port/testing"
      pageTitle mustBe "Test Page"
      click.on(find(name("b")).value)
      eventually { pageTitle mustBe "scalatest" }
    }
  }
}

如果您的每个测试都需要一个新的浏览器实例,请使用 `OneBrowserPerTest` 而不是 `OneBrowserPerSuite`。与 `OneBrowserPerSuite` 一样,您还需要混合 `ServerProvider` 和 `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"https://127.0.0.1:$port/testing")
      pageTitle mustBe "Test Page"
      click.on(find(name("b")).value)
      eventually { pageTitle mustBe "scalatest" }
    }
  }
}

如果您需要多个测试类共享同一个浏览器实例,请将 `OneBrowserPerSuite` 混合到主套件中,并将 `ConfiguredBrowser` 混合到多个嵌套套件中。嵌套套件将共享同一个 Web 浏览器。有关示例,请参阅 `ConfiguredBrowser` 特征的文档。

§在多个浏览器中运行相同的测试

如果您想在多个 Web 浏览器中运行测试,以确保您的应用程序在您支持的所有浏览器中都能正常工作,您可以使用特征 AllBrowsersPerSuiteAllBrowsersPerTest。这两个特征都声明了一个类型为 IndexedSeq[BrowserInfo]browsers 字段和一个抽象的 sharedTests 方法,该方法接受一个 BrowserInfobrowsers 字段指示您希望测试在哪些浏览器中运行。默认值为 Chrome、Firefox、Internet Explorer、HtmlUnit 和 Safari。如果默认值不符合您的需求,您可以覆盖 browsers。您将要在多个浏览器中运行的测试放在 sharedTests 方法中,并将浏览器名称放在每个测试名称的末尾。(浏览器名称可从传递到 sharedTestsBrowserInfo 中获取。)以下是一个使用 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"https://127.0.0.1:$port/testing"
        pageTitle mustBe "Test Page"
        click.on(find(name("b")).value)
        eventually { pageTitle mustBe "scalatest" }
      }
    }
  }
}

sharedTests 声明的所有测试都将在 browsers 字段中提到的所有浏览器中运行,只要它们在主机系统上可用。任何在主机系统上不可用的浏览器的测试将自动取消。

注意:您需要手动将 browser.name 附加到测试名称,以确保套件中的每个测试都有一个唯一的名称(这是 ScalaTest 所要求的)。如果您省略了它,在运行测试时会收到重复测试名称错误。

AllBrowsersPerSuite 将创建每种类型浏览器的单个实例,并将其用于 sharedTests 中声明的所有测试。如果您希望每个测试都有自己的全新浏览器实例,请改用 AllBrowsersPerTest

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"https://127.0.0.1:$port/testing")
        pageTitle mustBe "Test Page"
        click.on(find(name("b")).value)
        eventually { pageTitle mustBe "scalatest" }
      }
    }
  }
}

虽然 AllBrowsersPerSuiteAllBrowsersPerTest 都将取消对不可用浏览器类型的测试,但测试将在输出中显示为已取消。为了清理输出,您可以通过覆盖 browsers 来排除永远不可用的 Web 浏览器,如以下示例所示

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"https://127.0.0.1:$port/testing")
        pageTitle mustBe "Test Page"
        click.on(find(name("b")).value)
        eventually { pageTitle mustBe "scalatest" }
      }
    }
  }
}

前面的测试类将只尝试使用 Firefox 和 Chrome 运行共享测试(如果浏览器不可用,则会自动取消测试)。

§PlaySpec

PlaySpec 为 Play 测试提供了一个方便的“超级套件”ScalaTest 基类。通过扩展 PlaySpec,您可以自动获得 WordSpecMustMatchersOptionValuesWsScalaTestClient

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)
    }
  }
}

您可以将之前提到的任何特征混合到PlaySpec中。

§当不同的测试需要不同的夹具时

在之前示例中显示的所有测试类中,测试类中的所有或大多数测试都需要相同的夹具。虽然这很常见,但并非总是如此。如果同一个测试类中的不同测试需要不同的夹具,请混合使用特征MixedFixtures。然后使用以下无参数函数之一为每个单独的测试提供它所需的夹具:AppServerChromeFirefoxHtmlUnitInternetExplorerSafari

您不能将MixedFixtures混合到PlaySpec中,因为MixedFixtures需要一个ScalaTest fixture.Suite,而PlaySpec只是一个普通的Suite。如果您想要一个方便的混合夹具基类,请扩展MixedPlaySpec。以下是一个示例

// 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")) {
      override def running() = app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new App(buildApp("ehcacheplugin" -> "disabled")) {
      override def running() = 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")) {
      override def running() = app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new Server(buildApp("ehcacheplugin" -> "disabled")) {
      override def running() = getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new Server {
      override def running() = {
        import java.net._
        val url                    = new URI("https://127.0.0.1:" + port + "/boom").toURL
        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")) {
      override def running() = app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new HtmlUnit(buildApp("ehcacheplugin" -> "disabled")) {
      override def running() = getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new HtmlUnit {
      override def running() = {
        import java.net._
        val url                    = new URI("https://127.0.0.1:" + port + "/boom").toURL
        val con: HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection]
        try con.getResponseCode mustBe 404
        finally con.disconnect()
      }
    }
    "provide a web driver" in new HtmlUnit(buildApp()) {
      override def running() = {
        go to ("https://127.0.0.1:" + 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")) {
      override def running() = app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new Firefox(buildApp("ehcacheplugin" -> "disabled")) {
      override def running() = getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new Firefox {
      override def running() = {
        import java.net._
        val url                    = new URI("https://127.0.0.1:" + port + "/boom").toURL
        val con: HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection]
        try con.getResponseCode mustBe 404
        finally con.disconnect()
      }
    }
    "provide a web driver" in new Firefox(buildApp()) {
      override def running() = {
        go to ("https://127.0.0.1:" + 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")) {
      override def running() = app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new Safari(buildApp("ehcacheplugin" -> "disabled")) {
      override def running() = getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new Safari {
      override def running() = {
        import java.net._
        val url                    = new URI("https://127.0.0.1:" + port + "/boom").toURL
        val con: HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection]
        try con.getResponseCode mustBe 404
        finally con.disconnect()
      }
    }
    "provide a web driver" in new Safari(buildApp()) {
      override def running() = {
        go to ("https://127.0.0.1:" + 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")) {
      override def running() = app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new Chrome(buildApp("ehcacheplugin" -> "disabled")) {
      override def running() = getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new Chrome {
      override def running() = {
        import java.net._
        val url                    = new URI("https://127.0.0.1:" + port + "/boom").toURL
        val con: HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection]
        try con.getResponseCode mustBe 404
        finally con.disconnect()
      }
    }
    "provide a web driver" in new Chrome(buildApp()) {
      override def running() = {
        go to ("https://127.0.0.1:" + 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")) {
      override def running() = app.configuration.getOptional[String]("ehcacheplugin") mustBe Some("disabled")
    }
    "make the Application available implicitly" in new InternetExplorer(buildApp("ehcacheplugin" -> "disabled")) {
      override def running() = getConfig("ehcacheplugin") mustBe Some("disabled")
    }
    "send 404 on a bad request" in new InternetExplorer {
      override def running() = {
        import java.net._
        val url                    = new URI("https://127.0.0.1:" + port + "/boom").toURL
        val con: HttpURLConnection = url.openConnection().asInstanceOf[HttpURLConnection]
        try con.getResponseCode mustBe 404
        finally con.disconnect()
      }
    }
    "provide a web driver" in new InternetExplorer(buildApp()) {
      override def running() = {
        go to ("https://127.0.0.1:" + 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
    }
  }
}

§测试模板

由于模板是一个标准的Scala函数,您可以从测试中执行它,并检查结果

"render index template" in new App {
  override def running() = {
    val html = views.html.index("Coco")

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

§测试控制器

您可以通过提供FakeRequest来调用任何Action代码

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"
    }
  }
}

§测试路由器

您可以让Router来执行,而不是自己调用Action

"respond to the index Action" in new App(applicationWithRouter) {
  override def running() = {
    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")
  }
}

§测试模型

如果您使用的是SQL数据库,您可以使用Helpers#inMemoryDatabase将数据库连接替换为H2数据库的内存实例。

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

  override def running() = {
    val Some(macintosh) = Computer.findById(21)

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

§测试WS调用

如果您正在调用Web服务,您可以使用WSTestClient。有两个可用的调用,wsCallwsUrl,它们分别接受Call或字符串。请注意,它们需要在运行的应用程序上下文中调用。

wsCall(controllers.routes.Application.index()).get()
wsUrl("https://127.0.0.1:9000").get()

下一步: 使用specs2进行测试


在此文档中发现错误?此页面的源代码可以在此处找到。在阅读文档指南后,请随时贡献拉取请求。有疑问或建议要分享?转到我们的社区论坛与社区开始对话。