【问题标题】:Mocking a function of real object with Spock使用 Spock 模拟真实对象的功能
【发布时间】:2019-10-15 10:08:31
【问题描述】:

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

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')
  }
}

【问题讨论】:

  • 你看到了什么错误?
  • @tim_yates getMessage() 不会被模拟,并以真实对象的形式给出结果
  • 语法controller.getMessage() &lt;&lt; Spy(PublicController) { ... } 看起来非常错误。请也显示控制器代码。我不使用 Grails,但从我在测试文档中看到的情况来看,它不会像这样工作。 ControllerUnitTest 是用于提供控制器实例的特征,因此没有明显的方法可以用模拟对象替换该实例,因为该特征的目标是对控制器进行单元测试,即您可以模拟/存根它的依赖项,而不是被测对象本身。
  • 您所做的不是单元测试,而是集成测试。因此,您应该自己创建控制器(模拟)实例,在这种情况下不要使用 ControllerUnitTest 特征。我认为你在这里误解了一些东西。 P.S.:类没有“功能”,它们有方法。 ;-)
  • @kriegaex 我从你有用的教训中学到了。非常感谢!!

标签: grails groovy mocking spock


【解决方案1】:

免责声明:我对 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)
      ...  
   }

【讨论】:

  • 控制器是测试中的保留变量,无需编写任何初始化代码。
  • 是的,但它没有在问题中显示,我假设您在某处创建它,如果不是在测试中,那么可能在您项目的某些基础设施级别代码中,有人必须创建它并 spock单靠自己是做不到的,所以必须有一些扩展。如果是这样 - 如果您使用选项 2,您可以提供/使用现有的钩子并将依赖项注入控制器,否则您可以使用选项 1。
  • 它没有显示,因为它是在框架内部初始化的。
  • 所以我知道为框架提供挂钩不是一种选择?如果是这样,第一种方法是您可以继续使用的唯一方法...
猜你喜欢
  • 1970-01-01
  • 2021-09-17
  • 2019-08-29
  • 1970-01-01
  • 2016-05-23
  • 1970-01-01
  • 1970-01-01
  • 2015-03-20
  • 1970-01-01
相关资源
最近更新 更多