2

How can an annotated class be mocked (or spied) in Spock so that logic which relies on the annotation can be asserted?

A contrived example in Groovy (could be Java too) shows a method that checks for a specific annotation value in a collection of objects. Imagine more complex logic performed after filtering by annotation, which I'd like to assert by mocking objects in the collection.

@FooAnnotation('FOOBAR')
class MainGroovy {
    def findFOOBARs(Object... candidates) {
        candidates.findAll{ it.class.getAnnotation(FooAnnotation)?.value() == 'FOOBAR' }
        //Do something with the filtered objects.
    }
}

Passing a Spy fails the annotation filter, so nothing can be asserted about any subsequent logic.

@Unroll
def test() {
    given:
        def foobars = mg.findFOOBARs(mg, new Object(), 'STRING')
    expect:
        foobars.size() == 1
    where:
        mg << [new MainGroovy(), Spy(MainGroovy)]
}
jaco0646
  • 15,303
  • 7
  • 59
  • 83

1 Answers1

2

I might be not aware of something, but I've done a quick research of this use case. Looks like it's impossible to retain annotations from mocked/spied classes using Spock's Mock or Spy. If we look into how Spock creates mocks/spies of a class when byte-buddy is used, we'll see that it subclasses the original type. If we look deeper into how byte-buddy works by default, then we will see that it doesn't retain annotations of the original type unless configured otherwise. By default, it just uses InstrumentedType's Default Factory with subclass method ignoring annotations' retention.

I haven't found any issues related to the annotations retention on Spock's GitHub yet. A fix on Spock side might look quite trivial but I'm not sure about this. Better to ask on their GitHub.

As a quite ugly workaround for simple cases, you can try Mockito spy/mock in a Spock spec

Dmitry Khamitov
  • 3,061
  • 13
  • 21
  • 1
    This led me to a related question: [Retain annotations on CGLIB proxies?](https://stackoverflow.com/q/1706751/1371329) Adding `@Inherited` to my custom `@FooAnnotation` makes the test pass. – jaco0646 Mar 30 '19 at 20:27
  • Yes, but please be aware of the fact that this only works for annotations on class types, not on interface types (which you can also mock) or on method annotations etc. This is also documented in for the JDK. – kriegaex Apr 25 '19 at 01:32
  • @kriegaex, can you link to the pertinent section of the JDK doc? – jaco0646 Apr 28 '19 at 14:11
  • Of course. Sorry, I thought it would be easy and logical to just check the [Javadoc for the `@Inherited` annotation](https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Inherited.html). – kriegaex Apr 29 '19 at 01:42