grails 集成测试中的模拟方法会转移到其他测试
Mocking methods in grails integration test carries over to other tests
我有一个集成测试,有时我想模拟服务方法的 return。但是,我已经看到,一旦我模拟了该方法,调用它的后续测试也将使用模拟函数。
这正常吗?如果是这样,我怎样才能进行有时使用模拟函数有时使用真实实现的测试?
这是我的代码:
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()
}
}
一般来说,您不想在集成或功能测试中更改与元classes 相关的任何内容,而只是在单元测试中。预计您将在单元测试中执行此操作,并且会自动支持在每次测试后或每次测试 class 运行后恢复原始元 class,具体取决于 Grails 的版本和配置方式.但在集成测试中情况并非如此。
您可以使用多种不同的方法。如果您使用非类型化依赖注入,例如def someService
,那么你可以用任何你想要的东西覆盖真实的服务实例,只要它有你将在测试方法期间调用的方法,控制器就不会知道或关心它是不是真正的服务。
我喜欢在这种情况下使用闭包映射,因为 Groovy 会像调用方法一样调用闭包。所以对于 'test 1' 你可以这样做:
def "test 1"() {
...
def mockedService = [methodToMock: { args -> return ... }]
myController.someService = mockedService
...
myController.save()
}
这是可行的,因为您为每个测试获得了一个新的控制器实例,并且您仅为该实例更改了服务,但实际服务根本不受影响。
您的控制器调用 someService.methodToMock()
,实际上是 someService.get('methodToMock').call()
,但是映射访问和闭包调用语法可以利用 Groovy 的语法糖来看起来像常规方法打电话。
另一种选择是子class 服务并覆盖您想要的方法,然后用它替换注入的实例。如果您输入依赖项注入(例如 SomeService someService
),则需要这个或类似的东西。创建一个命名的 subclass (class TestSomeService extends SomeService { ... }
) 或创建一个匿名的内部 class:
def "test 1"() {
...
def mockedService = new SomeService() {
def methodToMock(args) {
return ...
}
}
myController.someService = mockedService
...
myController.save()
}
在一个测试中改变元类绝对会影响其他测试。您正在更改 groovy 系统,如果您正在元分类,则需要执行一些特殊的清理工作。在我使用 metaClass 的方法的最后,我调用一个函数来撤销 metaClass 更改,传入元类 class 的名称,如果有的话,还有实例 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
}
我有一个集成测试,有时我想模拟服务方法的 return。但是,我已经看到,一旦我模拟了该方法,调用它的后续测试也将使用模拟函数。
这正常吗?如果是这样,我怎样才能进行有时使用模拟函数有时使用真实实现的测试?
这是我的代码:
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()
}
}
一般来说,您不想在集成或功能测试中更改与元classes 相关的任何内容,而只是在单元测试中。预计您将在单元测试中执行此操作,并且会自动支持在每次测试后或每次测试 class 运行后恢复原始元 class,具体取决于 Grails 的版本和配置方式.但在集成测试中情况并非如此。
您可以使用多种不同的方法。如果您使用非类型化依赖注入,例如def someService
,那么你可以用任何你想要的东西覆盖真实的服务实例,只要它有你将在测试方法期间调用的方法,控制器就不会知道或关心它是不是真正的服务。
我喜欢在这种情况下使用闭包映射,因为 Groovy 会像调用方法一样调用闭包。所以对于 'test 1' 你可以这样做:
def "test 1"() {
...
def mockedService = [methodToMock: { args -> return ... }]
myController.someService = mockedService
...
myController.save()
}
这是可行的,因为您为每个测试获得了一个新的控制器实例,并且您仅为该实例更改了服务,但实际服务根本不受影响。
您的控制器调用 someService.methodToMock()
,实际上是 someService.get('methodToMock').call()
,但是映射访问和闭包调用语法可以利用 Groovy 的语法糖来看起来像常规方法打电话。
另一种选择是子class 服务并覆盖您想要的方法,然后用它替换注入的实例。如果您输入依赖项注入(例如 SomeService someService
),则需要这个或类似的东西。创建一个命名的 subclass (class TestSomeService extends SomeService { ... }
) 或创建一个匿名的内部 class:
def "test 1"() {
...
def mockedService = new SomeService() {
def methodToMock(args) {
return ...
}
}
myController.someService = mockedService
...
myController.save()
}
在一个测试中改变元类绝对会影响其他测试。您正在更改 groovy 系统,如果您正在元分类,则需要执行一些特殊的清理工作。在我使用 metaClass 的方法的最后,我调用一个函数来撤销 metaClass 更改,传入元类 class 的名称,如果有的话,还有实例 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
}