Stubbing methods returning
java.util.Optional
with Spock is more tricky that you would probably expect. Get know how to do it efficiently.
Introduction
One of the nice features of the mocking framework in Spock is an ability to return sensible default values for unstubbed method calls made on stubs. Empty list for a method returning List
, 0 for long
, etc. Very handy if you don’t care about returned value in a particular test, but for example would like to prevent NullPointerException
later in the flow. Unfortunately Spock 1.0 and 1.1-rc-2 (still compatible with Java 6) is completely not aware of types added in Java 8 (such as Optional
or CompletableFutures
). You may say “no problem” null
is acceptable in many cases, but with Optional
the situation is even worse.
Issue
Imaging the following code – method returning Optional
and a try to use it in a test:
interface Repository<T> { Optional<T> getMaybeById(long id) } @Ignore("Broken") def "should not fail on unstubbed call with Optional return type"() { given: Dao<Order> dao = Stub() when: dao.getMaybeById(5) then: noExceptionThrown() }
You may think – null
will be returned on the getMaybeById()
call, but it’s not.
Expected no exception to be thrown, but got 'org.spockframework.mock.CannotCreateMockException' at spock.lang.Specification.noExceptionThrown(Specification.java:119) at info.solidsoft.blog.spock10.other.CustomDefaultResponseSpec.should not fail on unstubbed call with Optional return type(CustomDefaultResponseSpec.groovy:19) Caused by: org.spockframework.mock.CannotCreateMockException: Cannot create mock for class java.util.Optional because Java mocks cannot mock final classes. If the code under test is written in Groovy, use a Groovy mock. at org.spockframework.mock.runtime.JavaMockFactory.createInternal(JavaMockFactory.java:49) at org.spockframework.mock.runtime.JavaMockFactory.create(JavaMockFactory.java:40) (...)
The test fails at runtime as Spock is not able to stub java.util.Optional
which is a final
class:
CannotCreateMockException: Cannot create mock for class java.util.Optional because Java mocks cannot mock final classes.
What we can do?
Two workarounds
The EmptyOrDummyResponse
factory class (which tries to be smart) is used by default for stubs when an ustubbed method is being called. However, it can be changed on demand during a stub creation:
def "should not fail on unstubbed call with Optional return type - workaround 1"() { given: Dao<Order> dao = Stub([defaultResponse: ZeroOrNullResponse.INSTANCE]) when: dao.getMaybeById(5) then: noExceptionThrown() }
This test will pass (getMaybeById()
just returned null
), but there is an easier way to achieve the same result.
Spock uses EmptyOrDummyResponse
only for stubs (created with a Stub()
method). For mocks (created with a Mock()
method) the ZeroOrNullResponse
factory is used (which makes sense as mocks should focus on interaction verification not just stubbing). Thanks to that a smart logic trying to return sensible default value is disabled in much simpler way:
def "should not fail on unstubbed call with Optional return type - workaround 2"() { given: Dao<Order> dao = Mock() when: dao.getMaybeById(5) then: noExceptionThrown() }
However, this workaround is far from being perfect. Firstly, your colleagues may be surprised why a mock is created while only stubbing is performed (by the way, both stubbing and verifying interaction on the same mock is tricky itself in Spock, but this is a topic for an another blog post). Secondly, wouldn’t it be nice to have an empty optional (instead of null
) returned by default?
Solution
In addition to an aforementioned way to use predefined factories for default return types Spock provides an ability to write a custom one. Let’s create EmptyOrDummyResponse
-life factory which is aware of Java 8 types. In fact, the implementation is very straightforward:
class Java8EmptyOrDummyResponse implements IDefaultResponse { public static final Java8EmptyOrDummyResponse INSTANCE = new Java8EmptyOrDummyResponse() private Java8EmptyOrDummyResponse() {} @Override public Object respond(IMockInvocation invocation) { if (invocation.getMethod().getReturnType() == Optional) { return Optional.empty() } //possibly CompletableFutures.completedFuture(), dates and maybe others return EmptyOrDummyResponse.INSTANCE.respond(invocation) } }
Our class implements an IDefaultResponse
interface with one respond()
method. Inside, we can apply custom logic for Optional
, CompletableFutures
and maybe other Java 8 specific types. As a fallback (for “standard” types) we switch to the original EmptyOrDummyResponse
. This code works as expected:
@SuppressWarnings("GroovyPointlessBoolean") def "should return empty Optional for unstubbed calls"() { given: Dao<Order> dao = Stub([defaultResponse: Java8EmptyOrDummy.INSTANCE]) when: Optional<Order> result = dao.getMaybeById(5) then: result?.isPresent() == false //NOT the same as !result?.isPresent() }
Please pay attention to consider Groovy truth implementation while making assertions with Optional
. !result?.isPresent()
would be fulfilled also for null
returned from a method.
However, maybe it would be good to simplify a Java 8 aware stub creation a little bit? To do that an extra method can be created:
private <T> T Stub8(Class<T> clazz) { return Stub([defaultResponse: Java8EmptyOrDummy.INSTANCE], clazz) } @SuppressWarnings("GroovyPointlessBoolean") def "should return empty Optional for unstubbed calls with Stub8"() { given: Dao<Order> dao = Stub8(Dao) when: Optional<Order> result = dao.getMaybeById(5) then: result?.isPresent() == false //NOT the same as !result?.isPresent() }
Unfortunately in in that case an enhanced more compact stub creation syntax available in Spock 1.1 cannot be used with our Stub8()
method. All because Spock will not be able to determine it’s type looking on he left side on assignment. In the end, however, it is much shorter than setting defaultResponse
in an every stub creation.
Please note that due to Spock limitations that method cannot be put in a trait (or a separate class) and has to be defined in the current test or a custom base (super) class for all the tests (extending itself spock.lang.Specification
), e.g.:
abstract class Java8AwareSpecification extends Specification { protected <T> T Stub8(Class<T> clazz) { ... } } class MyFancyTest extends Java8AwareSpecification { ... }
Summary
Thanks to exploring some Spock internals related to a stub and mock creation it was possible to enhance default strategy for smart responses for unstubbed calls to nicely support Java 8 features. This is just one of the topics I covered in my advanced “Interesting nooks and crannies of Spock you (may) have never seen before” presentation gave at Gr8Conf Europe 2016. You may want to see it :-).
Btw, the good news is that upcoming Spock 1.1(-rc-3) will contain native support for returning sensible default values for unstubbed Optional
method calls.
Self promotion. Would you like to improve your and your team testing skills and knowledge of Spock quickly and efficiently? I conduct a condensed (unit) testing training which you may find useful.