Spock 不使用 Groovy MetaClass 对待测服务的更改

Posted

技术标签:

【中文标题】Spock 不使用 Groovy MetaClass 对待测服务的更改【英文标题】:Groovy MetaClass change to Service Under Test is not used by Spock 【发布时间】:2014-06-19 17:35:09 【问题描述】:

在 Spock 单元测试中,我试图测试独立于 getGithubUrlForPath 的方法 findRepositoriesByUsername 的行为,它们都属于同一个服务。

多次尝试使用metaClass 均失败:

String.metaClass.blarg 产生错误 No such property: blarg for class: java.lang.String service.metaClass.getGithubUrlForPath修改服务实例无效 GithubService.metaClass.getGithubUrlForPath修改服务类不起作用 尝试在测试方法设置中的 metaClass 上添加/修改方法,当阻塞时,均未按预期工作

测试:

package grails.woot

import grails.test.mixin.TestFor

@TestFor(GithubService)
class GithubServiceSpec extends spock.lang.Specification 

    def 'metaClass test'() 
        when:
        String.metaClass.blarg =  -> 
            'brainf***'
        

        then:
        'some string'.blarg == 'brainf***'
    

    def 'can find repositories for the given username'() 
        given:
        def username = 'username'
        def requestPathParts

        when: 'the service is called to retrieve JSON'
        service.metaClass.getGithubUrlForPath =  pathParts ->
            requestPathParts = pathParts
        
        service.findRepositoriesByUsername(username)

        then: 'the correct path parts are used'
        requestPathParts == ['users', username, 'repos']
    


服务:

package grails.woot

import grails.converters.JSON

class GithubService 

    def apiHost = 'https://api.github.com/'

    def findRepositoriesByUsername(username) 
        try
            JSON.parse(getGithubUrlForPath('users', username, 'repos').text)
         catch (FileNotFoundException ex) 
            // user not found
        
    

    def getGithubUrlForPath(String ... pathParts) 
        "$apiHost$pathParts.join('/')".toURL()
    

我已经在 groovy shell(由 grails 启动)中测试了 String.metaClass.blarg 示例,结果符合预期。

我在这里有一个根本的误解吗?我究竟做错了什么?是否有更好的方法来处理所需的测试(替换被测服务上的方法)?

【问题讨论】:

【参考方案1】:

这是编写测试以使其通过的方式:

def 'metaClass test'() 
    given:
        String.metaClass.blarg =  -> 'brainf***' 

    expect:
        // note blarg is a method on String metaClass 
        // not a field, invoke the method
        'some string'.blarg() == 'brainf***'


def 'can find repositories for the given username'() 
    given:
        def username = 'username'
        def requestPathParts

    when: 'the service is called to retrieve JSON'
        service.metaClass.getGithubUrlForPath =  String... pathParts ->
            requestPathParts = pathParts
            [text: 'blah'] // mimicing URL class
        
        service.findRepositoriesByUsername(username)

    then: 'the correct path parts are used'
        requestPathParts == ['users', username, 'repos']

【讨论】:

谢谢! blarg 错误应该从错误中显而易见,但我完全错过了。 getGithubUrlForPath 问题似乎是闭包参数中缺少类型。现在这是有道理的!我想添加了一个新方法getGithubUrlForPath(Object pathParts),而不是替换现有的方法。【参考方案2】:

你为什么不使用 Spock 强大的 Mocking 能力呢?

看http://spockframework.github.io/spock/docs/1.0/interaction_based_testing.html#_creating_mock_objects

无需窥视元类本身,您可以创建一些存根对象,然后调用所需的方法而不是原来的方法。你也可以使用 Groovy 的 MockFor 和 StubFor,它们会更容易一些。

您不能完全信任 spock 测试规范中的元类。

    里面有一些复杂的逻辑,很容易把事情搞砸。尝试在调试器下运行一些测试,你会看到的。 Spock 在后台使用元类。它可以覆盖您自己的尝试。

【讨论】:

我想我仍然缺少一些东西......findRepositoriesByUsernamegetGithubUrlForPath 都属于同一个服务(正在测试中),前者调用后者。当findRepositoriesByUsername 调用它时,我不明白如何模拟来自getGithubUrlForPath 的响应。

以上是关于Spock 不使用 Groovy MetaClass 对待测服务的更改的主要内容,如果未能解决你的问题,请参考以下文章

使用Groovy+Spock轻松写出更简洁的单测

Compile Groovy/Spock with GMavenPlus

在 groovy (Spock) 中测试文件结构

我如何在 java 方法中运行 spock groovy 测试

Groovy Spock环境的安装

Groovy/Spock 测试导论