§使用 specs2 撰寫功能測試
Play 提供許多類別和便利方法,可協助進行功能測試。其中大部分都可以在 play.api.test
套件或 Helpers
物件中找到。
您可以透過匯入以下內容來新增這些方法和類別
import play.api.test._
import play.api.test.Helpers._
§建立 Application
執行個體以進行測試
Play 經常需要一個正在執行的 Application
作為內容。如果您使用預設的 Guice 依賴性注入,您可以使用 GuiceApplicationBuilder
類別,該類別可以設定為不同的設定檔、路由,甚至其他模組。
val application: Application = GuiceApplicationBuilder().build()
§WithApplication
若要將應用程式傳遞至範例,請使用 WithApplication
。可以傳遞明確的 Application
,但會提供預設應用程式 (從預設的 GuiceApplicationBuilder
建立) 以供方便使用。
由於 WithApplication
是內建的 Around
區塊,您可以覆寫它以提供自己的資料設定
abstract class WithDbData extends WithApplication {
override def wrap[T: AsResult](t: => T): Result = super.wrap {
setupData()
t
}
def setupData(): Unit = {
// setup data
}
}
"Computer model" should {
"be retrieved by id" in new WithDbData {
override def running() = {
// your test code
}
}
"be retrieved by email" in new WithDbData {
override def running() = {
// your test code
}
}
}
§WithServer
有時您會希望在測試中測試真正的 HTTP 堆疊,這種情況下您可以使用 WithServer
啟動測試伺服器
"test server logic" in new WithServer(app = applicationWithBrowser, port = testPort) {
override def running() = {
// The test payment gateway requires a callback to this server before it returns a result...
val callbackURL = s"http://$myPublicAddress/callback"
val ws = app.injector.instanceOf[WSClient]
// await is from play.api.test.FutureAwaits
val response =
await(ws.url(testPaymentGatewayURL).withQueryStringParameters("callbackURL" -> callbackURL).get())
response.status must equalTo(OK)
}
}
port
值包含伺服器執行的埠號。預設會指派一個隨機埠,不過你可以透過將埠傳遞到 WithServer
,或設定系統屬性 testserver.port
來變更。這對於整合持續整合伺服器很有用,這樣每個建置都可以動態保留埠。你也可以使用系統屬性 testserver.address
設定測試伺服器繫結的位址。如果未設定,它會使用 Play 伺服器的預設值 "0.0.0.0"
。
也可以將應用程式傳遞到測試伺服器,這對於設定自訂路由和測試 WS 呼叫很有用
val appWithRoutes = GuiceApplicationBuilder()
.appRoutes { app =>
val Action = app.injector.instanceOf[DefaultActionBuilder]
({
case ("GET", "/") =>
Action {
Ok("ok")
}
})
}
.build()
"test WSClient logic" in new WithServer(app = appWithRoutes, port = 3333) {
override def running() = {
val ws = app.injector.instanceOf[WSClient]
await(ws.url("https://127.0.0.1:3333").get()).status must equalTo(OK)
}
}
§WithBrowser
如果你想要使用瀏覽器來測試你的應用程式,你可以使用 Selenium WebDriver。Play 會為你啟動 WebDriver,並使用 FluentLenium 提供的便利 API 將其包裝在 WithBrowser
中。就像 WithServer
一樣,你可以變更埠、Application
,你也可以選擇要使用的網路瀏覽器
def applicationWithBrowser = {
new GuiceApplicationBuilder()
.appRoutes { app =>
val Action = app.injector.instanceOf[DefaultActionBuilder]
({
case ("GET", "/") =>
Action {
Ok("""
|<html>
|<body>
| <div id="title">Hello Guest</div>
| <a href="/login">click me</a>
|</body>
|</html>
""".stripMargin).as("text/html")
}
case ("GET", "/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) {
override def running() = {
browser.goTo("/")
// Check the page
browser.el("#title").text() must equalTo("Hello Guest")
browser.el("a").click()
browser.url must equalTo("login")
browser.el("#title").text() must equalTo("Hello Coco")
}
}
§Injecting
有許多功能測試會透過隱含的 app
直接使用注入器
"test" in new WithApplication() {
override def running() = {
val executionContext = app.injector.instanceOf[ExecutionContext]
executionContext must beAnInstanceOf[ExecutionContext]
}
}
使用 Injecting
特質,你可以省略它
"test" in new WithApplication() with play.api.test.Injecting {
override def running() = {
val executionContext = inject[ExecutionContext]
executionContext must beAnInstanceOf[ExecutionContext]
}
}
§PlaySpecification
PlaySpecification
是 Specification
的延伸,它會排除預設 specs2 規格中提供的部分混入,這些混入會與 Play 輔助函式方法衝突。它也會混入 Play 測試輔助函式和類型以提供便利性。
class 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)
}
}
}
§測試檢視範本
由於範本是標準 Scala 函式,你可以從你的測試執行它,並檢查結果
"render index template" in new WithApplication {
override def running() = {
val html = views.html.index("Coco")
contentAsString(html) must contain("Hello Coco")
}
}
§測試控制器
你可以透過提供 FakeRequest
來呼叫任何 Action
程式碼
"respond to the index Action" in new WithApplication {
override def running() = {
val controller = app.injector.instanceOf[scalaguide.tests.controllers.HomeController]
val result = controller.index()(FakeRequest())
status(result) must equalTo(OK)
contentType(result) must beSome("text/plain")
contentAsString(result) must contain("Hello Bob")
}
}
技術上來說,你不需要 WithApplication
,因為你可以直接實例化控制器,但直接實例化控制器比較像是控制器的單元測試,而不是功能測試。
§測試路由器
你可以讓 Router
來呼叫 Action
,而不是自己呼叫
"respond to the index Action" in new WithApplication(applicationWithRouter) {
override def running() = {
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")
}
}
§測試模型
如果你使用 SQL 資料庫,你可以使用 inMemoryDatabase
,以 H2 資料庫的記憶體中實例取代資料庫連線。
def appWithMemoryDatabase = new GuiceApplicationBuilder().configure(inMemoryDatabase("test")).build()
"run an application" in new WithApplication(appWithMemoryDatabase) {
override def running() = {
val Some(macintosh) = Computer.findById(21)
macintosh.name must equalTo("Macintosh")
macintosh.introduced must beSome[String].which(_ must beEqualTo("1984-01-24"))
}
}
§測試訊息 API
對於涉及設定的功能測試,最佳選擇是使用 WithApplication
,並注入 MessagesApi
"messages" should {
import play.api.i18n._
implicit val lang = Lang("en-US")
"provide default messages with the Java API" in new WithApplication() with Injecting {
override def running() = {
val javaMessagesApi = inject[play.i18n.MessagesApi]
val msg = javaMessagesApi.get(new play.i18n.Lang(lang), "constraint.email")
msg must ===("Email")
}
}
"provide default messages with the Scala API" in new WithApplication() with Injecting {
override def running() = {
val messagesApi = inject[MessagesApi]
val msg = messagesApi("constraint.email")
msg must ===("Email")
}
}
}
如果你需要自訂設定,最好將設定值新增到 GuiceApplicationBuilder
,而不是直接使用 DefaultMessagesApiProvider
。
下一頁:使用 Guice 進行測試
在這個文件中發現錯誤嗎?此頁面的原始程式碼可以在 這裡 找到。在閱讀 文件指南 後,歡迎貢獻一個 pull request。有問題或建議要分享嗎?前往 我們的社群論壇,與社群開始對話。