如何使用私有 属性 创建 Spock Spy

How to create Spock Spy with private property

我关注Java class:

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 class 创建 Spy 对象,但出现以下错误:

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

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

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

对于您的情况,最简单的解决方案是将此字段设置为 protected 而不是 private。当您从 class 创建间谍对象时,会涉及 CGLIB,它会从您尝试从 class 创建间谍的 class 创建为子 class 在您的案例中.问题是您要修改的字段是私有字段,根据 Java 中的常规子 classing 策略,私有字段不会在子 class 中继承。这就是为什么间谍对象

中不存在字段 barService

ATTENTION: IntelliJ's debugger may tell you that barService is present in this spyFromService instance, however this is IDE's bug - if you list all available fields from spyFromService.class.fields or spyFromService.class.declaredFields you wont find barService field here.

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

spyFromService.metaClass.barService = barService

spyFromService.class.metaClass.barService = barService

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

FooServiceImpl spyFromService = new FooServiceImpl()
spyFromService.@barService = barService

会起作用。但是,您将无法存根现有的 customFoo() 方法,您将不得不依赖于它的实际实现 returns.