【问题标题】:Groovy MetaClass change to Service Under Test is not used by SpockSpock 不使用 Groovy MetaClass 对待测服务的更改
【发布时间】: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 示例,结果符合预期。

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

【问题讨论】:

    标签: grails groovy spock metaclass


    【解决方案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 测试规范中的元类。

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

    【讨论】:

    • 我想我仍然缺少一些东西......findRepositoriesByUsernamegetGithubUrlForPath 都属于同一个服务(正在测试中),前者调用后者。当findRepositoriesByUsername 调用它时,我不明白如何模拟来自getGithubUrlForPath 的响应。
    猜你喜欢
    • 2016-06-12
    • 2010-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-09
    • 2017-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多