Spock – Starting your trek

Ready to add a little diversity to your Java testing? The Spock framework could be a good place to start.

If there was a “right way” then everyone would be doing it, right? Every project environment we encounter is driven by slightly different experience sets and therefore slightly different value sets. Each has its own priorities and its own views on what ‘good’ software is all about.

A signature trade-off in many projects is between utilising a diverse set of optimised tools-for-the job against a single tool-set and therefore simpler skill-set. Does “Keep It Simple” mean minimising your code-lines by leveraging the widest possible set of frameworks, libraries and languages or does it mean a single language and minimal set of libraries where as much logic as possible is in plain-sight and a code-monkey with nothing more than our primary language can pick it up in a thrice?

When it comes to a unit testing approach for a Java project this trade-off will probably mean the difference between every test being a JUnit test or each test leveraging one of any number of testing languages and tools that allow us to describe that test case in the clearest way possible. Groovy tests with or without specification frameworks like Spock can reduce syntax noise whilst frameworks like Cucumber can turn our tests into natural language specifications of our class under test’s behaviour.

While we may celebrate diversity at a social level, at a technical level is does come at a price which we should not ignore. Each additional library and language that finds its way into our build chain is in itself a “point of failure” that can cause conflicts and clashes as our platforms and packages advance. Each new technology we add is an additional technology that future maintainers will have to know or be capable of quickly learning; a particular concern when considering large-scale customer-facing applications where the time to fix a problem in production can carry a hefty cost. Additionally the more functionality we allow to be provided “under-the-hood” the less we can readily see what is taking place. Code brevity of this type brings about an obfuscation all of its own.

The search for Spock

If you’re considering embracing a little diversity in your tests then Groovy with the Spock test specification framework might be a good place to start. Groovy provides some great features for simplifying and clarifying code whilst still tolerating a lot of plain Java syntax and Spock provides a lightweight specification language atop it. And all this can be yours for the cost of a single extra Maven dependency.

By way of a simple example let’s start with a Java class to tax even the keenest Vulcan logic. Which came first, the chicken or the egg?

public class WhichCameFirst {

    private String a, b;

    public WhichCameFirst(String a, String b) {
        this.a = a; this.b = b;
    }

    public String whatComesBefore(String it) {
        if(it.equals(a) || it.equals(b)) {
            return it.equals(a) ? b : a;
        } else {
            throw new IllegalArgumentException();
        }
    }

    public String whatCameFirst() {
        throw new IllogicalException();
    }
}

Our class is pleasingly generic; not hard-coded to the Chicken and Egg conundrum it can handle any number of similarly structured noodle-scratchers – the Chicken or the Egg? The Bee or the Flower? The Actress or the Bishop?

Let’s kick off with a plain old JUnit 4 test for our class:-

public class WhichCameFirstTest {

    @Test
    public void testWhatComesBefore() {
        WhichCameFirst cut = new WhichCameFirst("Chicken", "Egg");
        assertEquals("Egg", cut.whatComesBefore("Chicken"));
        assertEquals("Chicken", cut.whatComesBefore("Egg"));
    }

    @Test(expected = IllegalArgumentException.class)
    public void testWhatComesBeforeIncorrectParameter() {
        WhichCameFirst cut = new WhichCameFirst("Chicken", "Egg");
        cut.whatComesBefore("Aardvark");
    }

    @Test(expected = NullPointerException.class)
    public void testWhatComesBeforeNullParameter() {
        WhichCameFirst cut = new WhichCameFirst("Chicken", "Egg");
        cut.whatComesBefore(null);
    }

    @Test(expected = IllogicalException.class)
    public void testWhatCameFirst() {
        WhichCameFirst cut = new WhichCameFirst("Chicken", "Egg");
        cut.whatCameFirst();
    }
}

We’ve got four test methods – a couple of positive tests and a couple of illegal argument cases. Now let’s convert it to a Spock specification.

First up we need to add Spock to our build. We’re using Maven here so we’ll just need to add a single dependency to pick it up:-

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.spockframework</groupId>
        <artifactId>spock-maven</artifactId>
        <version>0.7-groovy-2.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Spock provides a JUnit test runner so our tests will get picked up by our Maven test plugin and can be run in the same way as JUnit tests in our IDE. It’s dependent on Groovy and a few other bits and bobs but they’ll be pulled in for us automatically.

Next we create an src/test/groovy source folder and add our Spock Spec to it. We’ll kick off with the first positive test.

class WhichCameFirstTest extends spock.lang.Specification {
    def "What comes before"() {
        setup :
            def cut = new WhichCameFirst("Chicken", "Egg")
        when :
            def forebear = cut.whatComesBefore("Chicken")
        then :
            forebear == "Egg"
        when :
            forebear = cut.whatComesBefore("Egg")
        then :
            forebear == "Chicken"
    }
}

Each Spock specification is a Groovy class which extends the spock.lang.Specification class. Each of our tests now becomes a feature method with a natural language name and its parts clearly divided into meaningful blocks. Here we’re using setup: to configure our test fixture, when: to perform the actions we’re testing and then: to assert the post-conditions. 

We’re testing two different input values in this test method and we can clean up our test by making it data-driven instead. We use an expect: block to describe our conditions and a where: block to supply the values we want to use:-

def "What comes before"() {
    setup :
        def cut = new WhichCameFirst("Chicken", "Egg")
    expect :
        forebear == cut.whatComesBefore(descendent)
    where :
        forebear  | descendent
        "Chicken" | "Egg"
        "Egg"     | "Chicken"
}

Our other test methods look for expected exceptions which we checked for in our JUnit test by using the expected property on our @Test annotations. With Spock we use thrown() to check expected exceptions occurred:-

def "What comes before - no argument"() {
    setup :
        def cut = new WhichCameFirst("Chicken", "Egg")
    when :
        def forebear = cut.whatComesBefore(null);
    then :
        thrown(NullPointerException)
}

def "What comes before - invalid argument"() {
    setup :
        def cut = new WhichCameFirst("Chicken", "Egg")
    when :
        def forebear = cut.whatComesBefore("Aardvark");
    then :
         thrown(IllegalArgumentException)
}

def "What comes first"() {
    setup :
        def cut = new WhichCameFirst("Chicken", "Egg")
    when :
        def forebear = cut.whatCameFirst();
    then :
        thrown(IllogicalException)
}

Sure, measured purely in terms of code-lines our Spock spec is a touch more verbose than our JUnit version but certainly no less clear and readable as a result. When it comes to more complex, real-world scenarios though, combining the clarity of this specification approach with powerful Groovy features like closures and map based bean construction, we can really make our test cases leaner, fitter and yes, perhaps a little more logical.

Leave a Reply

Your email address will not be published. Required fields are marked *