Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create Spock Spy with private property

Tags:

java

groovy

spock

I have following Java class:

public class FooServiceImpl {
    private BarService barService;

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

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

And here is exemplary Spock test method:

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"
}

I try to create a Spy object for FooServiceImpl class but I get following error:

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

I can't add a constructor to FooServiceImpl or setter for BarService, so I want to use map constructor. Is this possible?

Note: according to this issue it should work

like image 328
A5300 Avatar asked Nov 08 '22 08:11

A5300


1 Answers

The easiest solution in your case would be to make this field protected instead of private. When you create a spy object from a class, CGLIB is involved and it creates as subclass from the class you are trying to create spy from - com.foo.FooServiceImpl$$EnhancerByCGL in your case. The thing is that the field you are trying to modify is a private field and according to regular subclassing strategy in Java, private field does not get inherited in child class. That is why field barService does not exist in spy object

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.

Another problem is that when CGLIB gets involved in object creation process it also gets involved if it comes to invoking methods. This is why adding dynamic fields to a class or instance via Groovy's metaprogramming features wont work. Otherwise you would be able to do things like:

spyFromService.metaClass.barService = barService

or

spyFromService.class.metaClass.barService = barService

Alternatively you could get rid of spy object and use a real instance in your test. Then

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

will work. However you won't be able to stub existing customFoo() method and you will have to rely on what its real implementation returns.

like image 136
Szymon Stepniak Avatar answered Nov 14 '22 22:11

Szymon Stepniak