Spock tricks – Simplified Java 8 Optional stubbing with custom default responses

Posted: 2017-03-15 in Tools, Tricks & Tips
Tags: , , , ,

Stubbing methods returning java.util.Optional with Spock is more tricky that you would probably expect. Get know how to do it efficiently.

by Infrogmation, Wikimedia Commons, public domain

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 worst.

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 easiest 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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s