§使用 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("http://localhost:3333").get()).status must equalTo(OK)
}
}
§使用浏览器
如果你想使用浏览器测试你的应用程序,你可以使用 Selenium WebDriver。Play 会为你启动 WebDriver,并使用 FluentLenium 提供的便捷 API 将其包装起来,使用 WithBrowser
。就像 WithServer
一样,你可以更改端口、Application
,你也可以选择要使用的 Web 浏览器。
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")
}
}
§注入
许多功能测试直接通过隐式 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 助手方法冲突的混合。
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
,因为你可以直接实例化控制器 - 然而,直接控制器实例化更像是对控制器的单元测试,而不是功能测试。
§测试路由器
与其自己调用 Action
,不如让 Router
来做。
"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 进行测试
发现此文档中的错误?此页面的源代码可以在 此处 找到。在阅读 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?前往 我们的社区论坛 与社区进行交流。