【问题标题】:Play Framework, Specs2 - Calling controller method directly from unit testPlay Framework,Specs2 - 直接从单元测试调用控制器方法
【发布时间】:2013-11-02 15:18:10
【问题描述】:

我的控制器中有一个我想直接调用的方法。它接受一个 POSTed 表单,对其进行验证,然后返回一些东西。我想直接测试这个 - 即不通过路线助手。

这是我的表单代码(FormFields 只是一个案例类)

val searchForm = Form(
  mapping(
   "foo" -> nonEmptyText,
   "filter" -> optional(text).verifying("Filter text must be 'published' or 'unpublished'",
     x => x.isEmpty || x.get == "published" || x.get == "unpublished")
 )(FormFields.apply)(FormFields.unapply)

)

这是我的控制器调用。

def doThings() = IsAuthenticated {
   username => implicit request => {
    searchForm.bindFromRequest().fold(
      formWithErrors => BadRequest(s"Incorrect data: ${formWithErrors.errors.map(x => s"${x.key} ${x.message}").mkString}."),
      form => {
            OK("Test text here")
      }
    )
  }

}

如果我通过我的路由文件调用它,如下所示 - 这可以按预期工作。表单已按预期发布、验证并返回 OK("Test...")。

即。以下工作(使用 Specs2)

        val request = FakeRequest(POST, "notarealurl")
          .withFormUrlEncodedBody(
          "filter" -> "published",
          "foo" -> "Test"
    ).withSession("email" -> "testuser")

    val Some(result) = route(request)
    status(result) must equalTo(OK)

但是,无论我尝试直接调用该方法都会失败 - 失败发生在表单验证步骤上。当我运行单元测试时,它告诉我“foo”缺少一个值。这就是我尝试这样做的方式。

    val request = FakeRequest()
      .withFormUrlEncodedBody(
      "filter" -> "published",
      "foo" -> "Test"
    ).withSession("email" -> "testuser")


    //val Some(result) = route(request)
    val result = Search.searchProducts()(request)

    println(contentAsString(result))
    status(result) must equalTo(OK)

打印的文本是“不正确的搜索:foo error.required”。我认为我没有正确拨打电话,但我不知道我哪里出错了。

注意:这里的代码代表我的问题,但已被删减以说明问题。

【问题讨论】:

  • 我知道你已经说过你不想使用路线助手。但是,如果你真的使用它,它会起作用吗?
  • @LimbSoup 是的,它适用于路线助手。

标签: scala post playframework playframework-2.0 specs2


【解决方案1】:

我模仿了你的逻辑,它运行良好。我用 Play 文档中的复制粘贴替换了您的一些代码,只是为了保持最小化。我在我现在正在处理的设置之上对其进行了测试 因此您会看到与默认 Play 设置不同的伪影。此设置或多或少与我最初链接的文章中描述的设置相同。我不知道如何比这更直接:

在控制器中:

import play.api.data._
import play.api.data.Forms._
case class UserData(name: String, age: Int)
val userFormConstraints2 = Form(
  mapping(
    "name" -> nonEmptyText,
    "age" -> number(min = 0, max = 100)
  )(UserData.apply)(UserData.unapply)
)
def test = Action {
  implicit request => {
    userFormConstraints2.bindFromRequest().fold(
      formWithErrors => BadRequest("bad"),
      userData => {
        Ok(userData.name + userData.age)
      }
    )
  }
}

测试:

class TempSpec extends Specification with MyHelpers {
  "1" can {
    "direct access to controller while posting" in new TestServer {
                        // `in new TestServer` spawns dependencies (`server`)
      val controller = new controllers.Kalva(server)
                        // I instantiate the controller passing the dependency
      val request = FakeRequest(POST, "bla")
        .withFormUrlEncodedBody(
          "name" -> "Richard",
          "age" -> "1"
        )
      val result = controller.test()(request)
      status(result) must equalTo(OK)
      contentAsString(result) must contain("Richard");
      val request_bad = FakeRequest(POST, "bla")
        .withFormUrlEncodedBody(
          "name" -> "",
          "age" -> "-1"
        )
      val result_bad = controller.test()(request_bad)
      status(result_bad) must equalTo(400)
      contentAsString(result_bad) must contain("bad");
    }
  }
}

Global.scala:

object Global extends GlobalSettings {
  private lazy val injector = Guice.createInjector(new TestModule)

  override def getControllerInstance[A](controller: Class[A]) = {
    injector.getInstance(controller)
  }
}

测试模块:

import com.google.inject._
import com.tzavellas.sse.guice.ScalaModule
class TestModule extends ScalaModule {
  def configure() {
    @Provides
    def getServer:Server = {
      ...
    }
  }
}

routes 文件内:

POST    /bla                        @controllers.Kalva.test
               // the `@` prefix is needed because of how we fetch controllers

原答案如下:


class TranslateSpec extends Specification {

  "Translate" should {
    // The normal Play! way
    "accept a name, and return a proper greeting" in {
      running(FakeApplication()) {
        val translated = route(FakeRequest(GET, "/greet/Barney")).get

        status(translated) must equalTo(OK)
        contentType(translated) must beSome.which(_ == "text/html")
        contentAsString(translated) must contain ("Barney")  
      }
    }

      // Providing a fake Global, to explitly mock out the injector
    object FakeTranslatorGlobal extends play.api.GlobalSettings {
      override def getControllerInstance[A](clazz: Class[A]) = {
        new Translate(new FakeTranslator).asInstanceOf[A]
      }
    }
    "accept a name, and return a proper greeting (explicitly mocking module)" in {
      running(FakeApplication(withGlobal = Some(FakeTranslatorGlobal))) {
        val home = route(FakeRequest(GET, "/greet/Barney")).get
        contentAsString(home) must contain ("Hello Barney")
      }
    }

    // Calling the controller directly, without creating a new FakeApplication
    // (This is the fastest)
    "accept a name, and return a proper greeting (controller directly, no FakeApplication!)" in {
      val controller = new Translate(new FakeTranslator)
      val result = controller.greet(name = "Barney")(FakeRequest())
      contentAsString(result) must contain ("Hello Barney")
    }
  }
}

上面的代码是非常自我描述的,它显示了默认的测试工作流程以及如何使用依赖注入来改进它。引用自this article

这段特别摘录来自“为什么我应该在 Play 中使用 DI?”部分。这篇文章是关于使用 Play2 设置 Google Guice 以及它带来的可能性。这是一本实用的读物。

正如您在上面看到的,“正常的 Play! 方式”很好,但是通过采用 DI,您可以在测试(当然还有一般的开发)中摆脱更多困境。

如文章中所述,在 Play 中使用 Guice 需要对 Play 的默认设置进行细微更改,这非常值得。我已经这样做了很长时间了,并且没有回头。

【讨论】:

  • 我认为这并不能真正解决我的问题。我的问题是无法访问控制器,我可以直接调用它(类似于您答案中的第三种方法),但是当我调用“val result = Search.searchProducts()(request)”时有问题做一个 POST。
  • 如果您在running(FakeApplication()) { 中运行它,它会起作用吗?我以为那不见了。使用 DI 可以让您从 FakeApplication 和所有路由混乱中解脱出来。没有它如何直接访问?
  • 其实少一点DI,多一点就是把控制器从对象变成类。
  • 我尝试过使用 FakeApplication(),行为相同。直接访问只是调用控制器路由到的方法(类似于您获得的方式(val result = controller.greet(...))。问题仅发生在 POST 请求上,因为主体似乎没有正确通过。GET 请求工作正常。在直接调用控制器时,您是否曾经设法验证过 POSTed 表单(即通过播放表单帮助程序读取一些字段)?
  • @Ren 我测试了它并且它有效,我用我使用的设置更新了答案。
猜你喜欢
  • 1970-01-01
  • 2013-12-30
  • 2014-04-12
  • 2017-04-04
  • 2023-03-03
  • 2016-04-20
  • 1970-01-01
  • 1970-01-01
  • 2015-10-06
相关资源
最近更新 更多