首页 > 解决方案 > 使用 Spock 模拟真实对象的功能

问题描述

我想为使用 Grails 框架编写的 Web 应用程序添加一些控制器单元测试。
我需要跳过控制器getMessage()中方法中使用的cancel_request()方法作为真实对象。

class PublicControllerSpec extends Specification implements ControllerUnitTest<PublicController> {

 def "Cancel Request for user"() {
    given:
    controller.getMessage() << Spy(PublicController){
        getMessage(_ as User, _ as String) >> "SuccessMessage"
    }

    when:
    controller.cancel_request()

    then:
    response.redirectedUrl.contains('/public/list')
  }
}

标签: grailsgroovymockingspock

解决方案


免责声明:我对 Grails 框架没有任何经验,但曾经使用过 Spock

根据您的问题:

你说:控制器是一个真实的对象,所以它应该在测试中用这样的代码创建:

controller = new SomeController(<dependencies>)

但是然后您指定对真实对象的期望 - IMO 它不会像这样工作,您可以仅在代理(存根、模拟、间谍)上指定期望,它们在实现中具有该功能,而真实对象显然没有。

所以基本上你有两种方法:

选项 1:监视控制器

控制器本身必须是一个间谍:这是包装真实对象(真实控制器)的东西,默认情况下将所有方法调用委托给该内部真实对象,除非您不“抑制”此功能(在这种情况下,您说这样的话:表现得像一个真实的对象,但如果有人打电话getMessage,那么返回一条消息而不是委托给真实的对象)。这种方式的明显缺点是被测对象(控制器)变成了间谍,这不是一个好方法。

选项 2:重构代码

我承认,由于我没有使用 Grails 的经验,我可能会在这里遗漏一些东西,但如果控制器是您编写的代码,您可能会选择以下更改:

引入一个依赖:一个负责消息计算的类,例如:

 interface MessageCalculator {
     String getMessage()
 }

 class MyController {
      MessageCalculator messageCalculator 
      MyController(MessageCalculator messageCalculator) {
         this.messageCalculator = messageCalculator
      }


      public getMessage() {
           messageCalculator.getMessage()
      }
 }

旁注:是的,我知道这里可以应用 AST 转换,但这不是问题的主题,所以我会留下这样的代码

无论如何,现在您可以按如下方式重新实现测试:

   def "test me now"() {
      given:
         def msgCalc = Stub(MessageCalculator)
         msgCalc.getMessage() >> "Successfull message"

         def controller = new MyController(msgCalc)
      ...  
   }

推荐阅读