如何使用私有财产创建Spock间谍

时间:2018-04-13 13:29:44

标签: java groovy spock

我有以下Java类:

public class FooServiceImpl {
    private BarService barService;

    public String generateFoo() {
        String barValue = barService.generateBar();
        return customFoo() + barValue;
    }

    public String customFoo() {
       return "abc";
    }
}

以下是示例Spock测试方法:

def "generate foo bar"() {
    setup:
        def barService = Mock(BarService) {
            generateBar() >> "Bar"
        }
        FooServiceImpl spyFooService = 
          Spy(FooServiceImpl, constructorArgs: [[barService: barService]])

        spyFooService.customFoo() >> "foo"
    when:
        def fooValue = spyFooService.generateFoo()
    then:
        fooValue == "fooBar"
}

我尝试为FooServiceImpl类创建一个Spy对象,但是我收到了以下错误:

org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack: 
No such property: barService for class: 
com.foo.FooServiceImpl$$EnhancerByCGL`

我无法为FooServiceImpl添加构造函数或为BarService添加setter,因此我想使用map构造函数。这可能吗?

注意:根据this issue它应该有效

1 个答案:

答案 0 :(得分:0)

在您的情况下,最简单的解决方案是使此字段protected而不是private。当您从类创建间谍对象时,CGLIB会参与其中,并且它会从您尝试在您的案例中创建间谍的类创建子类 - com.foo.FooServiceImpl$$EnhancerByCGL。问题是您尝试修改的字段是私有字段,并且根据Java中的常规子类策略,私有字段不会在子类中继承。这就是间谍对象

中不存在字段barService的原因
  

注意: IntelliJ的调试器可能会告诉您barService实例中存在spyFromService,但这是IDE的错误 - 如果列出{{1}中的所有可用字段或} spyFromService.class.fields你在这里找不到spyFromService.class.declaredFields字段。

另一个问题是,当CGLIB参与对象创建过程时,如果涉及调用方法,它也会涉及到。这就是为什么通过Groovy的元编程功能向类或实例添加动态字段不起作用的原因。否则你将能够做到这样的事情:

barService

spyFromService.metaClass.barService = barService

或者你可以摆脱间谍对象并在你的测试中使用一个真实的实例。然后

spyFromService.class.metaClass.barService = barService

会奏效。但是,您将无法存根现有的FooServiceImpl spyFromService = new FooServiceImpl() spyFromService.@barService = barService 方法,并且您将不得不依赖其实际实现返回的内容。