如何使用私有财产创建Spock Spy

A53*_*300 5 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";
    }
}
Run Code Online (Sandbox Code Playgroud)

这是示例性的 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"
}
Run Code Online (Sandbox Code Playgroud)

我尝试为FooServiceImpl类创建一个 Spy 对象,但出现以下错误:

org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack: 
No such property: barService for class: 
com.foo.FooServiceImpl$$EnhancerByCGL`
Run Code Online (Sandbox Code Playgroud)

我无法添加构造函数FooServiceImpl或设置器BarService,所以我想使用映射构造函数。这可能吗?

注意:根据这个问题,它应该可以工作

Szy*_*iak 1

对于您的情况,最简单的解决方案是使该字段protected代替private. 当您从类创建间谍对象时,会涉及 CGLIB,并且它会从您尝试从中创建间谍的类创建子类 -com.foo.FooServiceImpl$$EnhancerByCGL在您的情况下。问题是,您尝试修改的字段是私有字段,并且根据 Java 中的常规子类化策略,私有字段不会在子类中继承。这就是为什么barService间谍对象中不存在字段的原因

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

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

spyFromService.metaClass.barService = barService
Run Code Online (Sandbox Code Playgroud)

或者

spyFromService.class.metaClass.barService = barService
Run Code Online (Sandbox Code Playgroud)

或者,您可以摆脱间谍对象并在测试中使用真实实例。然后

FooServiceImpl spyFromService = new FooServiceImpl()
spyFromService.@barService = barService
Run Code Online (Sandbox Code Playgroud)

将工作。但是,您将无法存根现有customFoo()方法,并且必须依赖其实际实现的返回值。