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
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 thisspyFromService
instance, however this is IDE's bug - if you list all available fields fromspyFromService.class.fields
orspyFromService.class.declaredFields
you wont findbarService
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With