【问题标题】:Mocking methods in grails integration test carries over to other testsgrails 集成测试中的模拟方法会延续到其他测试
【发布时间】:2016-04-13 09:37:16
【问题描述】:

我有一个集成测试,有时我想模拟服务方法的返回。但是,我已经看到,一旦我模拟了该方法,调用它的后续测试也将使用模拟函数。

这正常吗?如果是这样,我怎样才能测试有时使用模拟函数,有时使用真实实现?

这是我的代码:

MyController {
    def someService

    def save(){
        ...
        def val = someService.methodToMock()//sometimes want to mock other times, not
        ...
    }
}

MyTest {

    def "test 1"(){
        ...
        //I want to mock here
        myController.someService.metaClass.methodToMock = { [] }
        ...
        myController.save()
    }

    def "test 2"(){
        ...
        //I don't want to mock here, however 
        // it is returning the mocked results
        myController.save()
    }
}

【问题讨论】:

    标签: grails mocking integration-testing


    【解决方案1】:

    一般来说,您不想在集成或功能测试中更改与元类有关的任何内容,仅在单元测试中进行更改。预计您将在单元测试中执行此操作,并且自动支持在每次测试之后或在每个测试类运行之后恢复原始元类,具体取决于 Grails 的版本和配置方式。但在集成测试中并非如此。

    您可以使用多种不同的方法。如果您使用无类型的依赖注入,例如def someService,然后你可以用你想要的任何东西覆盖真实的服务实例,只要它有你将在测试方法期间调用的方法,控制器就不会知道或关心它不是真正的服务。

    在这种情况下,我喜欢使用闭包映射,因为 Groovy 会像调用方法一样调用闭包。因此,对于“测试 1”,您可以这样做:

    def "test 1"() {
        ...
        def mockedService = [methodToMock: { args -> return ... }]
        myController.someService = mockedService
        ...
        myController.save()
    }
    

    这是可行的,因为您为每个测试获取了一个新的控制器实例,并且您仅为该实例更改了服务,但真正的服务根本不受影响。

    您的控制器调用someService.methodToMock(),实际上是someService.get('methodToMock').call(),但映射访问和闭包调用语法可以利用Groovy 的语法糖,看起来像一个常规方法调用。

    另一个选择是子类化服务并覆盖您想要的方法,并用它替换注入的实例。如果您键入依赖注入(例如SomeService someService),则需要此或类似的内容。创建命名子类 (class TestSomeService extends SomeService { ... }) 或创建匿名内部类:

    def "test 1"() {
        ...
        def mockedService = new SomeService() {
            def methodToMock(args) {
                return ...
            }
        }
        myController.someService = mockedService
        ...
        myController.save()
    }
    

    【讨论】:

      【解决方案2】:

      在一项测试中更改元类绝对会影响其他测试。您正在更改 groovy 系统,如果您在进行元分类,则需要执行一些特殊的清理。在我的 metaClass 方法的最后,我调用一个函数来撤销 metaClass 的更改,传入被 metaClassed 的类的名称,如果有,则传入 metaClassed 实例。

      def "some authenticated method test"() {
          given:
              def user = new UserDomain(blah blah blah)
              controller.metaClass.getAuthenticatedUser = { return user } 
          when:
              controller.authenticatedMethod() // which references the authenticated user
          then:
              // validate the results
          cleanup:
              revokeMetaClassChanges(theControllerClass, controller)
      }
      
      private def revokeMetaClassChanges(def type, def instance = null) {
          GroovySystem.metaClassRegistry.removeMetaClass(type)
          if (instance != null) {
              instance.metaClass = null
          }
      }
      

      或者,您可以在测试中模拟服务。类似于 Burt 提到的方法可能是:

      def "some test"() {
          given:
              def mockSomeService = mockFor(SomeService)
              mockSomeService.demand.methodToMock(1) { def args ->
                  return []
              }
              controller.someService = mockSomeService.createMock()
          when:
              controller.save()
          then:
              // implement your validations/assertions
      }
      

      【讨论】:

      • 这对我不起作用(Grails 2.5.6)。只有在测试方法中添加 @ConfineMetaClassChanges([...]) 才有帮助。 (reference)
      猜你喜欢
      • 1970-01-01
      • 2023-03-31
      • 1970-01-01
      • 1970-01-01
      • 2023-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-25
      相关资源
      最近更新 更多