【问题标题】:How do I unit test a controller in play framework 2 scala如何在 play framework 2 scala 中对控制器进行单元测试
【发布时间】:2013-07-09 11:47:32
【问题描述】:

假设我有一个控制器,其动作接收两个参数。

它调用两个服务,每个参数一个,服务都返回字符串

每个字符串都作为参数传递给模板

结果传递给 Ok 并返回。

我想编写一个简单的单元测试来确保: 1 - 使用正确的参数调用正确的服务 2 - 服务的返回值被传递给模板的正确属性

最好的方法是什么?

【问题讨论】:

  • 为什么不使用 ScalaCheck/ScalaTest/Specs2.0 ?
  • 具体来说,如何使用specs2.0编写测试来拦截对服务的调用和对模板的调用?我能够看到的示例测试整个应用程序(集成)

标签: unit-testing scala playframework-2.0


【解决方案1】:

将 Mockito 与 Specs2 结合使用,我模拟服务以验证它们的方法调用。

我的控制器是由 Spring 实例化的。这使我可以将其视为class 而不是object。 => 这对于使controller 可测试至关重要。举个例子:

@Controller
class MyController @Autowired()(val myServices: MyServices) extends Controller

要为控制器启用 Spring,您必须定义一个 Global 对象,作为 Play!文档说明:

object Global extends GlobalSettings {

  val context = new ClassPathXmlApplicationContext("application-context.xml")

  override def getControllerInstance[A](controllerClass: Class[A]): A = {
    context.getBean(controllerClass)
  }
}

我的单元测试不需要 Spring;我只是将合作者(模拟)传递给构造函数。

但是,关于呈现的模板,我只测试结果的类型(Ok、BadRequest、Redirection 等...)。 事实上,我注意到让我的测试详细扫描整个渲染模板(发送给它的参数等)并不容易,只需要单元测试。

因此,为了断言使用正确的参数调用了正确的模板,我相信我运行 Selenium 的验收测试或可能的功能测试(如果您愿意的话)会扫描整个预期结果。

2 - 来自服务的返回值被传递给正确的 模板的属性

这很容易检查..如何?通过信任编译器!更喜欢将一些自定义类型传递给您的模板,而不是简单的原语,例如: phone: String 将变为:phone: Phone。 (一个简单的值对象)。 因此,不必担心以非预期的顺序将属性传递给您的模板(在单元测试或实际生产代码中)。编译器确实会发出警告。

这是我使用 specs2 进行的一项单元测试(简化)的示例: (您会注意到包装器的使用:WithFreshMocks)。 这个case class 将允许在测试后刷新所有变量(在本例中为模拟)测试。 因此是重置模拟的好方法。

    class MyControllerSpec extends Specification with Mockito {

      def is =
        "listAllCars should retrieve all cars" ! WithFreshMocks().listAllCarsShouldRetrieveAllCars

      case class WithFreshMocks() {

        val myServicesMock = mock[MyServices]
        val myController = new MyController(myServicesMock)

        def listAllCarsShouldRetrieveAllCars = {
          val FakeGetRequest = FakeRequest() //fakeRequest needed by controller
          mockListAllCarsAsReturningSomeCars()
          val result = myController.listAllCars(FakeGetRequest).asInstanceOf[PlainResult] //passing fakeRequest to simulate a true request
          assertOkResult(result).
            and(there was one(myServicesMock).listAllCars()) //verify that there is one and only one call of listAllCars. If listAllCars would take any parameters that you expected to be called, you could have precise them.
        }

        private def mockListAllCarsAsReturningSomeCars() { 
           myServicesMock.listAllCars() returns List[Cars](Car("ferrari"), Car("porsche"))
        }

        private def assertOkResult(result: PlainResult) = result.header.status must_== 200

       }

【讨论】:

  • 我想出了一个允许我模拟我的模板的解决方案,但我赞成使用类型系统来获取编译器错误,而不是在使用不正确的参数时测试错误
【解决方案2】:

所以,我想出了一个基于蛋糕图案和 mockito 的解决方案:

提供服务:

trait Service {
  def indexMessage : String
}

trait ServiceImpl {
  def indexMessage = {
    "Hello world"
  }
}

那么控制器看起来像:

object Application extends ApplicationController
                   with ServiceImpl  {
  def template = views.html.index.apply
}

trait ApplicationController extends Controller
                            with Service {
  def template: (String) => play.api.templates.Html

  def index = Action {
    Ok(template("controller got:" + indexMessage))
  }
}

测试看起来像:

class ApplicationControllerSpec extends Specification with Mockito {
  "cake ApplicationController" should {
      "return OK with the results of the service invocation" in {
        val expectedMessage = "Test Message"
        val m = mock[(String) => play.api.templates.Html]

        object ApplicationControllerSpec extends ApplicationController {
          def indexMessage = expectedMessage
          def template = m
        }

        val response = ApplicationControllerSpec.index(FakeRequest())

        status(response) must equalTo(OK)
        there was one(m).apply(Matchers.eq("controller got:" + expectedMessage))
      }
  }
}

我在让 Mockito 工作时遇到了很多麻烦。
它需要一个额外的依赖,而且我在解决如何在 scala 中使用匹配器时遇到了很多麻烦(我在 java 中使用它很舒服)

最终我认为上面的答案更好,避免使用 String 和其他可以将它们包装在任务特定类型中的原始类型,然后你会收到编译器警告。

此外,我通常会避免在控制器中添加“控制器得到:”前缀。

在这种情况下它就在那里,所以我可以验证它在现实世界中应该由其他一些组件完成(控制器仅用于管道 IMO)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-14
    • 2013-09-16
    • 2014-04-12
    • 2017-12-11
    • 1970-01-01
    • 2021-03-19
    • 2015-07-13
    • 1970-01-01
    相关资源
    最近更新 更多