explores testing features of Groovy and some other testing frameworks

Groovy is great for Agile development in general and testing in particular because:

  • it has built-in support for the JUnit testing framework
  • it has built-in mocking capabilities
  • it provides a very expressive language in which to write tests which can utilise Closure syntax and Groovy's other features which support the creation of testing domain specific languages (DSLs)
  • it can easily leverage Java testing frameworks and IDE support
  • it's built-in AntBuilder support makes it easy to set up integration tests

This page explores testing features of Groovy and some other testing frameworks that you may sometimes wish to use to complement Groovy's built-in capabilities.

Two main examples are used:

  • A Stack Example
  • An Item Storer Example

Some of the examples on this page rely on annotations: a feature in Groovy 1.5+ when used with Java 5 and above.

A Stack Example

Inspired by the RSpec stack example, suppose we want to test the following Groovy classes:

class StackOverflowException extends RuntimeException {}

class StackUnderflowException extends RuntimeException {}

class FixedStack {
    public static MAXSIZE = 10
    private items = []

    def push(object) {
        if (items.size() == MAXSIZE) throw new StackOverflowException()
        items << object
    }

    def pop() {
        if (!items) throw new StackUnderflowException()
        items.pop()
    }

    def peek() {
        if (!items) throw new StackUnderflowException()
        items[-1]
    }

    boolean isEmpty() {
        items.isEmpty()
    }

    boolean isFull() {
        items.size() == MAXSIZE
    }

}

We can test this with vanilla Groovy using the following tests:

class NonEmptyFixedStackTest extends GroovyTestCase {
    private stack

    void setUp() {
        stack = new FixedStack()
        [ "a", "b", "c" ].each{ x -> stack.push x }
    }

    void testPreConditions() {
        assert !stack.isEmpty()
    }

    void testShouldAddToTheTopWhenSentPush() {
        stack.push "d"
        assert stack.peek() == "d"
    }

    void testShouldBeUnchangedWhenSentPushThenPop() {
        stack.push "anything"
        stack.pop()
        assert stack.peek() == "c"
    }

    void testShouldReturnTheTopItemWhenSentPeek() {
        assert stack.peek() == "c"
    }

    void testShouldNotRemoveTheTopItemWhenSentPeek() {
        assert stack.peek() == "c"
        assert stack.peek() == "c"
    }

    void testShouldReturnTheTopItemWhenSentPop() {
        assert stack.pop() == "c"
    }

    void testShouldRemoveTheTopItemWhenSentPop() {
        assert stack.pop() == "c"
        assert stack.pop() == "b"
    }

}

class EmptyFixedStackTest extends GroovyTestCase {
    private stack = new FixedStack()

    void testPreConditions() {
        assert stack.isEmpty()
    }

    void testShouldNoLongerBeEmptyAfterPush() {
        stack.push "anything"
        assert !stack.isEmpty()
    }

    void testShouldComplainWhenSentPeek() {
        shouldFail(StackUnderflowException) {
            stack.peek()
        }
    }

    void testShouldComplainWhenSentPop() {
        shouldFail(StackUnderflowException) {
            stack.pop()
        }
    }
}

class FullFixedStackTest extends GroovyTestCase {
    private stack

    void setUp() {
        stack = new FixedStack()
        (1..FixedStack.MAXSIZE).each{ x -> stack.push x }
    }

    void testPreConditions() {
        assert stack.isFull()
    }

    void testShouldRemainFullAfterPeek() {
        stack.peek()
        assert stack.isFull()
    }

    void testShouldNoLongerBeFullAfterPop() {
        stack.pop()
        assert !stack.isFull()
    }

    void testShouldComplainOnPush() {
        shouldFail(StackOverflowException) {
            stack.push "anything"
        }
    }
}

class AlmostFullFixedStackTest extends GroovyTestCase {
    private stack

    void setUp() {
        stack = new FixedStack()
        (1..<FixedStack.MAXSIZE).each{ x -> stack.push x }
    }

    void testPreConditions() {
        assert !stack.isFull()
    }

    void testShouldBecomeFullAfterPush() {
        stack.push "anything"
        assert stack.isFull()
    }
}

class AlmostEmptyFixedStackTest extends GroovyTestCase {
    private stack

    void setUp() {
        stack = new FixedStack()
        stack.push "anything"
    }

    void testPreConditions() {
        assert !stack.isEmpty()
    }

    void testShouldRemainNotEmptyAfterPeek() {
        stack.peek()
        testPreConditions()
    }

    void testShouldBecomeEmptyAfterPop() {
        stack.pop()
        assert stack.isEmpty()
    }
}

Of course, even within vanilla Groovy we have a few options. E.g., we could use no test class, just a script, in which case, the naming conventions required by JUnit 3.x (used above) wouldn't apply. Or, we could have used JUnit 4 and annotations to allow us to use alternate naming conventions for the methods. Alternatively, we could use a single test class with different test fixtures, e.g. fullStack and emptyStack. The other frameworks mentioned below allow additional possibilities for organising and naming our tests.

An Item Storer Example

Suppose we have the following Java interface (Groovy supports interface-oriented programming as well as dynamic programming using duck-typing and here we want to illustrate Java test/mock libraries later):

// Java
public interface Reverser {
    Object reverse(Object item);
}

Now suppose we have an implementation method as follows:

class GroovyReverser implements Reverser {
    def reverse(item) {
        if (item instanceof Number) return -item
        return item.reverse()
    }
}

For numbers, the reverse() method will negate them. Thanks to duck-typing, other objects that we try to reverse will just call their respective types reverse() method if it exists, e.g. so it will work for String and List objects.

Now suppose we make use of a reverser in some code we are trying to test.

class Storer {
    def stored
    Reverser reverser = new GroovyReverser()
    def put(item) {
        stored = item
    }
    def get() {
        return stored
    }
    def getReverse() {
        return reverser.reverse(stored)
    }
}

Integration Testing

We can integration test this class as follows:

def checkPersistAndReverse(cut, value, reverseValue) {
    cut.put(value)
    assert value == cut.get()
    assert reverseValue == cut.getReverse()
}

def testBehavior(cut) {
    checkPersistAndReverse cut, 123.456, -123.456
    checkPersistAndReverse cut, 'hello', 'olleh'
    checkPersistAndReverse cut, [1, 3, 5], [5, 3, 1]
}

testBehavior(new Storer())

Mocking Groovy Classes

The above integration tests exercise our class under test with the production reverser in place. For this particular example, we might argue that such a test is appropriate and sufficient. However, in more complicated scenarios, the dependent collaboration class (GroovyReverser in this case) might be difficult or expensive to construct. In those cases, we would want to replace it with a mock or stub. See Groovy Mocks for a definition of terms.

Here is how we would use Groovy's built-in mock support to test our class under test in isolation:

import groovy.mock.interceptor.MockFor

def mockReverser = new MockFor(GroovyReverser.class)
mockReverser.demand.reverse{ arg -> -arg }
mockReverser.demand.reverse{ arg -> arg.reverse() }
mockReverser.use {
    testBehavior(new Storer())
}

Note that we didn't need to do anything to inject our mocks into the class under test. Inside the use method, all attempts to create a GroovyReverser object will be replaced by a mock object. This also works for Java objects created by Groovy objects, as the following shows:

mockReverser = new MockFor(JavaReverser.class)
mockReverser.demand.reverse{ arg -> -arg }
mockReverser.demand.reverse{ arg -> arg.reverse() }
mockReverser.use {
    testBehavior(new Storer(reverser:new JavaReverser()))
}

Here JavaReverser is a class that we have defined as follows:

import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;

public class JavaReverser implements Reverser {
    public Object reverse(Object item) {
        if (item instanceof Number) {
            Number n = (Number) item;
            return new Double(-n.doubleValue());
        }
        if (item instanceof String) {
            String s = (String) item;
            int size = s.length();
            StringBuffer sb = new StringBuffer(size);
            for (int i = size - 1; i >= 0; i--) {
                sb.append(s.charAt(i));
            }
            return sb.toString();
        }
        if (item instanceof List) {
            List l = (List) item;
            int size = l.size();
            List result = new ArrayList(size);
            ListIterator iter = l.listIterator(size);
            while (iter.hasPrevious()) {
                result.add(iter.previous());
            }
            return result;
        }
        throw new UnsupportedOperationException();
    }
}

Hmmm. Quite a bit longer than the Groovy version. But that's another story.

Testing Java classes which use other Java classes

Now, consider now the following Java class:

public class JavaStorer {
    Object stored;
    Reverser reverser;

    public JavaStorer(Reverser reverser) {
        this.reverser = reverser;
    }

    void put(Object item) {
        stored = item;
    }
    Object get() {
        return stored;
    }
    Object getReverse() {
        return reverser.reverse(stored);
    }
}

We now want to test this too and we want to use Groovy to write the tests. Unfortunately, Groovy's built-in mock support won't help us here. It doesn't allow us to test Java classes like this one as it relies on hooking into Groovy's object lifecycle mechanisms. Instead, we can use any of the available Java mocking packages such as the ones mentioned here.

GSpec

GSpec is a testing framework written in Groovy, which this example is based on. See this page for a full explanation of using GSpec for BDD style testing.