文件

§使用 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,而不是共用同一個,請改用 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,如前一個範例所示。

最後,如果允許多個測試類別共用同一個伺服器,會比 OneServerPerSuiteOneServerPerTest 方法有更好的效能,則可以定義一個主套件,將 OneServerPerSuite 和混合 ConfiguredServer 的巢狀套件混合在一起,如 ConfiguredServer 文件中的範例所示。

§使用網路瀏覽器進行測試

ScalaTest + Play 函式庫建構在 ScalaTest 的 Selenium DSL 上,讓您能輕鬆地從網路瀏覽器測試您的 Play 應用程式。

若要使用同一個瀏覽器執行測試類別中的所有測試,請將 OneBrowserPerSuite 混合到您的測試類別中。您還需要混合一個 BrowserFactory 特質,它將提供 Selenium 網路驅動程式:ChromeFactoryFirefoxFactoryHtmlUnitFactoryInternetExplorerFactorySafariFactory 之一。

除了混合 BrowserFactory 之外,您還需要混合一個 ServerProvider 特質,它提供 TestServerOneServerPerSuiteOneServerPerTestConfiguredServer 之一。

例如,以下測試類別會混合 OneServerPerSuiteHtmUnitFactory

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 一樣,您也需要混合 ServerProviderBrowserFactory

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 混合到多個巢狀套件中。巢狀套件都將共用同一個網頁瀏覽器。如需範例,請參閱 特徵 ConfiguredBrowser 的文件

§在多個瀏覽器中執行相同的測試

如果您想在多個網頁瀏覽器中執行測試,以確保您的應用程式在您支援的所有瀏覽器中都能正確運作,您可以使用特徵 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 來排除永遠不會可用的網路瀏覽器,如以下範例所示

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 提供一個方便的「超級套件」ScalaTest 基底類別,供 Play 測試使用。透過延伸 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,它們分別會接收呼叫或字串。請注意,它們預期會在執行中應用程式的內容中呼叫。

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

下一步:使用 specs2 進行測試


在此文件當中發現錯誤?此頁面的原始程式碼可以在 這裡 找到。在閱讀 文件指南 之後,請隨時提交拉取請求。有問題或建議要分享嗎?請前往 我們的社群論壇 與社群展開對話。