Spock for the Java Developer: An Introduction

by
Tom Litton, Senior Software Engineer
Object Computing, Inc. (OCI)

Introduction

The release of JUnit more than a decade ago revolutionized testing on the Java platform. Since then there have been many advances in testing. Test driven development (TDD) and lately behavior driven development (BDD) have been gaining in popularity. Mocking became ubiquitous with the release of EasyMock and JMock. JUnit theories make tests more concise and maintainable. In addition to the framework and methodology changes, many people have been advocating using a dynamic languages (such as Ruby or Groovy) for test code.

Spock incorporates many concepts from all of these, and provides a groovy based DSL that is much more concise and fluent.

Domain

Before diving into examples, an explanation of the domain is necessary. In honor of the name Spock, all examples will be centered around the command computer from Star Trek. Users enter commands that are translated into actions across the ship's systems. For example, going to red alert triggers certain actions: playing the "red alert" sound, bringing weapons and shields online, etc.

The implementation used is not complete. It will have a simple life cycle with two states: running and stopped. It will implement a single command: red alert. It will have the ability to execute the red alert command automatically based on inputs from the sensors.

The initial implementation is very simple and more features are added over time.

Environment Setup

Spock will run with most build tools. The example below uses Maven:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>net.spockByExample</groupId>
    <artifactId>SpockByExample</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.spockframework</groupId>
            <artifactId>spock-core</artifactId>
            <version>0.6-groovy-1.8-SNAPSHOT</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.gmaven</groupId>
                <artifactId>gmaven-plugin</artifactId>
                <version>1.3</version>
                <configuration>
                    <providerSelection>1.8</providerSelection>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.spockframework</groupId>
                <artifactId>spock-maven</artifactId>
                <version>0.6-groovy-1.8-SNAPSHOT</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>find-specs</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>Spock</id>
            <name>Spock</name>
            <url>http://m2repo.spockframework.org/</url>
            <snapshots><enabled>true</enabled></snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>Spock</id>
            <name>Spock</name>
            <url>http://m2repo.spockframework.org/snapshots/</url>
            <snapshots><enabled>true</enabled></snapshots>
        </pluginRepository>

    </pluginRepositories>

</project>
    

A bit verbose (it is XML after all), but not too difficult. One thing to note, there is a separate Spock version for each release of Groovy (use 0.6-groovy-1.7 for Groovy 1.7.X and 0.6-groovy-1.8 for Groovy 1.8.X). The 3 places in which the Groovy version is specified are in bold.

The repositories are needed because a snapshot version is used. They can be removed once the 0.6 version is released.

Anatomy of a Test

Specification: A specification is the test class. The term specification describes perfectly how to think about a test. It defines the behavior or "spec" of a class or component and ensures the code adheres to that specification. All specifications must extend spock.lang.Specification base class.

Feature Method: Feature methods are the tests themselves. They are the methods that would be annotated with @Test in JUnit. Spock searches for feature methods by looking for any method that contains a block (blocks are explained...well...now).

Block: Blocks are the sections of a feature method. They break the test into sections. For example, there are blocks for setting up a test, exercising the code under test, and assertions.

Condition: Conditions are similar to assertions in JUnit. Statements in certain blocks (namely the then and expect blocks) are assumed to be conditions if they evaluate to a boolean. If any condition returns false, the test fails.

Behavior Driven Development (BDD) Concepts

BDD is an extension of TDD (Test Driven Development). Some prefer to think of it as advanced TDD (or TDD done right). Where TDD focuses on white box testing, BDD focuses on the behavior and business value.

The benefits of BDD (and TDD) have been outlined numerous times. There is no need bore you with them here. Only the relevant basics are covered. If a refresher is needed (or you are not familiar with the concept), you can get an overview here: http://behaviour-driven.org/.

BDD is organized in scenarios. A scenario is identified by a single sentence. BDD scenarios are always in the format:
Given some initial context (the givens),
When an event occurs,
Then ensure some outcomes.

Spock borrows many key concepts from BDD, and can be used for BDD very successfully. However, it is designed to be a general testing framework, and is a great deal more flexible than other BDD frameworks.

The examples below test this simple command computer implementation:

    public class CommandComputer {
        public static List<String> commands = Arrays.asList("Engage Warp", "Initiate Orbit", "Red Alert", "Yellow Alert");

        private boolean isRunning = false;

        private String station;

        public CommandComputer() {
        }

        public CommandComputer(String station) {
            this.station = station;
        }

        public List<String> listCommands() {
            return commands;
        }

        public void start() {
            isRunning = true;
        }

        public void stop() {
            isRunning = false;
        }

        public boolean isRunning() {
            return isRunning;
        }

        public String getStation() {
            return station;
        }

        public void execute(String command) {
            if(commands.contains(command)) {
                // @TODO execute command
            }
            else {
                throw new IllegalArgumentException("Invalid command: " + command);
            }
        }
    }

A simple test for the listCommands() method is:

package net.spockByExample.basics

import spock.lang.Specification

class ListCommandsTest extends Specification {
    void "listCommands returns all commands"() {
        given:
        CommandComputer commandComputer = new CommandComputer()
        commandComputer.start()

        when:
        List<String> commands = commandComputer.listCommands()

        then:
        commands == ["Engage Warp", "Initiate Orbit", "Red Alert", "Yellow Alert"]
    }
}

There are three blocks in the above example each specified by a label: given, when, and then. As the example shows, the names of the blocks match the parts of a BDD test:
Given a running command computer
When a list of commands is requested
Then the available commands are returned.

The output is (in Intellij Idea):

list commands results

The names of the blocks are the most obvious BDD concept, however, the base class spock.lang.Specification can also be considered one. It isn't just a list of tests for the CommandComputer class. It's a specification describing the behavior of the CommandComputer.

Spock allows developers to go a bit further into the BDD realm. Take this example:

void "listCommands returns all commands after the command computer is started"() {
    given:  "A new command computer."
    CommandComputer commandComputer = new CommandComputer()

    and: "The command computer is started."
    commandComputer.start()

    when: "The list of commands is requested."
    List<String> commands = commandComputer.listCommands()

    then: "The list of valid commands is returned."
    commands == ["Engage Warp", "Initiate Orbit", "Red Alert", "Yellow Alert"]
}

The descriptions are a nice place to document the requirements of a system or class. Currently Spock does not use them. However, there are plans to use them for reporting in the future.

In addition there is a new block: and. It behaves like an extra given block. It is a way of clearly delimiting the list of givens for a test.

Conditions

The then block validates the behavior of the class. Statements in the then block that return a boolean are evaluated as conditions. The test fails if any condition returns false.

In the test above, the then block asserts the list of commands (returned by the listCommands() invocation in the when block) is correct.

Conditions keep track of what they are evaluating to report the exact difference. Returning an invalid list gives:

failure results

Statements that do not return a boolean behave as expected. For example, the then block could have been:

then:
List<String> expectedList = ["Engage Warp", "Initiate Orbit", "Red Alert", "Yellow Alert"]
commands == expectedList

JUnit Similarities

In many ways Spock is a departure from the JUnit style of testing. However, there are many similarities.

Spock uses JUnit under the covers. The same IDE, build tool, continuous integration tool, etc., will continue to work.

A nice improvement in JUnit was the ability to consolidate common code into setUp() and tearDown() methods (now the @Before and @After annotations). Similarly, Spock has the methods setup() and cleanup(). Like JUnit, the setup() method is called before each test and the cleanup() method is called after each test. There is also the setupSpec() that is called before any test runs and the cleanupSpec() that is called after all tests run.

One important departure from JUnit is how Spock handles exceptions. JUnit uses an annotation to determine if an exception is expected to be thrown by the test:

@Test(expected=IOException.class)

This can be error prone. What happens if the test has further asserts? They are almost always below the code that throws the exception, and therefore, never run.

Spock takes a different approach. Recall the execute method:

public void execute(String command) {
    if(commands.contains(command)) {
        // TODO:  execute command
    }
    else {
        throw new IllegalArgumentException("Invalid command: " + command);
    }
}

The test is:

void "executing invalid command throws InvalidArgumentException"() {
    given:
    CommandComputer commandComputer = new CommandComputer()
    commandComputer.start()

    when:
    commandComputer.execute("Invalid")

    then:
    IllegalArgumentException e = thrown()
    e.message == "Invalid command: Invalid"
    
}

The thrown() method validates a specific exception is thrown. The exception can be assigned to a variable and used in other conditions.

If the exception instance is not necessary use:

thrown(IllegalArgumentException)

Conditions after the thrown() method will still run.

Parameterizations

Parameterizations are similar in concept to JUnit Theories.

Testing any method that accepts parameters can be problematic. To be complete, all variations of the parameter should be tested. Even a simple method can have several tests associated with it. The same is true for methods that depend on the object's state.

Take the execute method from the above example. In addition to an invalid command, there should be a test for each valid command.

A test this small may be manageable with private methods. However, it doesn't take much more to make maintenance a nightmare. Imagine a method that takes an entire object graph with many fields.

For example, the command computer should have the ability to automagically go to red alert based on updates from the sensors. A simple implementation might be:

public class SensorReading {
    private EntityType entityType;
    private PowerReading powerReading;
    private PowerType powerType;
    private FriendOrFoe friendOrFoe;
    private Direction direction;

    public EntityType getEntityType() {
        return entityType;
    }

    public void setEntityType(EntityType entityType) {
        this.entityType = entityType;
    }

    public PowerReading getPowerReading() {
        return powerReading;
    }

    public void setPowerReading(PowerReading powerReading) {
        this.powerReading = powerReading;
    }

    public PowerType getPowerType() {
        return powerType;
    }

    public void setPowerType(PowerType powerType) {
        this.powerType = powerType;
    }

    public FriendOrFoe getFriendOrFoe() {
        return friendOrFoe;
    }

    public void setFriendOrFoe(FriendOrFoe friendOrFoe) {
        this.friendOrFoe = friendOrFoe;
    }

    public Direction getDirection() {
        return direction;
    }

    public void setDirection(Direction direction) {
        this.direction = direction;
    }
}
public class CommandComputer {
    public static List<String> commands = Arrays.asList("Engage Warp", "Initiate Orbit", "Red Alert", "Yellow Alert");

    private boolean isRunning = false;

    private String station;

    private AlertStatus alertStatus = AlertStatus.GREEN;

    public CommandComputer() {
    }

    public CommandComputer(String station) {
        this.station = station;
    }

    public List<String> listCommands() {
        return commands;
    }

    public void start() {
        isRunning = true;
    }

    public void stop() {
        isRunning = false;
    }

    public boolean isRunning() {
        return isRunning;
    }

    public String getStation() {
        return station;
    }

    public AlertStatus getAlertStatus() {
        return alertStatus;
    }

    public void execute(String command) {
        if(commands.contains(command)) {
            // execute command
        }
        else {
            throw new IllegalArgumentException("Invalid command: " + command);
        }
    }

    
    public void updateFromSensors(SensorReading sensorReading) {

        if (
            sensorReading.getEntityType() == EntityType.SHIP
                    && sensorReading.getFriendOrFoe() == FriendOrFoe.FOE
                    && sensorReading.getPowerType() == PowerType.WEAPONS
        ) {
            alertStatus = AlertStatus.RED;
        }

    }
    
}

A JUnit test might look like:

    @Test
    public void shipGoesToRedAlertWhenEnemyChargesWeapons() {
        SensorReading reading = new SensorReading();
        reading.setDirection(Direction.TOWARD_SHIP);
        reading.setEntityType(EntityType.SHIP);
        reading.setFriendOrFoe(FriendOrFoe.FOE);
        reading.setPowerReading(PowerReading.HIGH);
        reading.setPowerType(PowerType.WEAPONS);

        CommandComputer commandComputer = new CommandComputer();
        commandComputer.updateFromSensors(reading);

        assertThat(commandComputer.getAlertStatus(), is(AlertStatus.RED));
    }

    @Test
    public void shipDoesntGoToRedAlertWhenPowerReadingLow() {
        SensorReading reading = new SensorReading();
        reading.setDirection(Direction.AWAY_FROM_SHIP);
        reading.setEntityType(EntityType.ANOMALY);
        reading.setFriendOrFoe(FriendOrFoe.UNKNOWN);
        reading.setPowerReading(PowerReading.LOW);
        reading.setPowerType(PowerType.RADIATION);

        CommandComputer commandComputer = new CommandComputer();
        commandComputer.updateFromSensors(reading);

        assertThat(commandComputer.getAlertStatus(), is(AlertStatus.GREEN));
    }

    // @TODO - a lot more tests!

Most of the code is duplicated between the tests. They can be improved by using private methods, but even that will become unmanageable if there is a test for each possible combination of SensorReading.

Spock solves this problem by allowing parameters to be used for test execution. From the first example in the section, testing that each valid command can execute without the IllegalArgumentException being thrown looks like:

@Unroll({"Testing command $command"})
void "ensure all commands can execute without error"() {

    given:
    CommandComputer commandComputer = new CommandComputer()
    commandComputer.start()

    when:
    commandComputer.execute(command)

    then:
    notThrown(IllegalStateException)

    
    where:
    command << ["Engage Warp", "Invalid", "Red Alert", "Yellow Alert"]
    

}

The values for the parameters are bound in the where block.

Every element in the list binds to the variable command, and the test runs for that value (for a total of 4 runs).

The @Unroll annotation causes the test execution for each command to be reported separately. The parameters bound in the where block can be used in the value of @Unroll using the $param syntax. An appropriate default is used if the value is left off.

The results are: param results with unroll

Or if an error occurs: param results with unroll failure

With out the annotation, it's difficult to to tell which value failed: param result with out unroll

The sensor reading test is a bit more complicated, but follows the same principles:

@Unroll
void "Ship goes to red alert only when enemy charges weapons"() {
    given:
    CommandComputer commandComputer = new CommandComputer()

    and:
    SensorReading sensorReading = new SensorReading()
    sensorReading.setEntityType(entityType)
    sensorReading.setPowerReading(powerReading)
    sensorReading.setFriendOrFoe(friendOrFoe)
    sensorReading.setPowerType(powerType)
    sensorReading.setDirection(direction)


    when:
    commandComputer.updateFromSensors(sensorReading)

    then:
    commandComputer.getAlertStatus() == expectedAlertStatus

    
    where:
    entityType   << [EntityType.SHIP,       EntityType.ANOMALY]
    powerReading << [PowerReading.HIGH,     PowerReading.LOW]
    friendOrFoe  << [FriendOrFoe.FOE,       FriendOrFoe.UNKNOWN]
    powerType    << [PowerType.WEAPONS,     PowerType.RADIATION]
    direction    << [Direction.TOWARD_SHIP, Direction.AWAY_FROM_SHIP]


    expectedAlertStatus << [AlertStatus.RED, AlertStatus.GREEN]
    
}

Data Tables

There is a slight problem with the test above. It handles a fraction of the possible cases. Adding many combinations to the lists will quickly become unmaintainable. Fortunately, Spock has a feature for the occasion:

@Unroll
void "conditions that cause red alert"() {
    given:
    CommandComputer commandComputer = new CommandComputer()

    
    SensorReading sensorReading = new SensorReading()
    sensorReading.setEntityType(entityType)
    sensorReading.setPowerReading(powerReading)
    sensorReading.setFriendOrFoe(friendOrFoe)
    sensorReading.setPowerType(powerType)
    sensorReading.setDirection(direction)
    

    when:
    commandComputer.updateFromSensors(sensorReading)

    then:
    commandComputer.getAlertStatus() == AlertStatus.RED

    
    where:
    entityType         | powerReading        | powerType           | friendOrFoe         | direction
    EntityType.SHIP    | PowerReading.HIGH   | PowerType.WEAPONS   | FriendOrFoe.FOE     | Direction.TOWARD_SHIP
    EntityType.SHIP    | PowerReading.MEDIUM | PowerType.WEAPONS   | FriendOrFoe.FOE     | Direction.AWAY_FROM_SHIP
    // other conditions that cause red alert ...
    

}

The test runs with data from each row in the "table".

It is functionally the same as:

where:
entityType   << [EntityType.SHIP,       EntityType.SHIP]
powerReading << [PowerReading.HIGH,     PowerReading.MEDIUM]
powerType    << [PowerType.WEAPONS,     PowerType.WEAPONS]
friendOrFoe  << [FriendOrFoe.FOE,       FriendOrFoe.FOE]
direction    << [Direction.TOWARD_SHIP, Direction.AWAY_FROM_SHIP]

Interactions

Interactions are Spock's mocking framework. A full explanation of mocks and how they are used is beyond the scope of this article. In short, mocks are "fake" objects that are substituted for real objects in order to test the interactions between two classes or components.

When EasyMock and JMock were released a few years ago, they quickly became ubiquitous across the Java testing community. It's a testament to how useful mocking is. However, neither API is particularly intuitive or fluent. Mockito is a great improvement over both frameworks, but is still far from perfect.

All mocking frameworks work by creating a mock object: a proxy for a class or interface. The mock object is then substituted for the real object on the class being tested. The test runs, and the mock records the methods invoked on it. The test then verifies the correct methods were invoked with the correct parameters. If necessary, return values (or exceptions) can be specified for the mocked methods before the test runs.

Take, for example, this implementation of executing the red alert command:

public interface EnvironmentSystem {

    /**
     * Plays the sound through out the ship.
     *
     * @param name
     */
    public void playSound(String name);
}

public interface ShieldSystem {

    /**
     * Raises the shields immediately.  The return value is the strength of the shields in terms of percent at
     * the time they are raised.
     *
     * The return value is in the range of 0 to 100
     *
     * @param shieldFrequency
     * @return
     */
    public int raiseShields(Integer shieldFrequency);
}
public class CommandComputer {
    public static List<String> commands = Arrays.asList("Engage Warp", "Initiate Orbit", "Red Alert", "Yellow Alert");

    private boolean isRunning = false;

    private String station;

    private AlertStatus alertStatus = AlertStatus.GREEN;

    private EnvironmentSystem environmentSystem;
    private ShieldSystem shieldSystem;

    private int shieldFrequency;


    public CommandComputer() {
    }

    public CommandComputer(String station) {
        this.station = station;
    }

    public EnvironmentSystem getEnvironmentSystem() {
        return environmentSystem;
    }

    public void setEnvironmentSystem(EnvironmentSystem environmentSystem) {
        this.environmentSystem = environmentSystem;
    }

    public ShieldSystem getShieldSystem() {
        return shieldSystem;
    }

    public void setShieldSystem(ShieldSystem shieldSystem) {
        this.shieldSystem = shieldSystem;
    }

    public int getShieldFrequency() {
        return shieldFrequency;
    }

    public void setShieldFrequency(int shieldFrequency) {
        this.shieldFrequency = shieldFrequency;
    }

    public List<String> listCommands() {
        return commands;
    }

    public void start() {
        isRunning = true;
    }

    public void stop() {
        isRunning = false;
    }

    public boolean isRunning() {
        return isRunning;
    }

    public String getStation() {
        return station;
    }

    public AlertStatus getAlertStatus() {
        return alertStatus;
    }

    public String execute(String command) {
        if(commands.contains(command)) {
            if(command.equalsIgnoreCase("Red Alert")) {
                return redAlert();
            }
            else {
                return "";
            }
        }
        else {
            throw new IllegalArgumentException("Invalid command: " + command);
        }
    }

    
    private String redAlert() {
        alertStatus = AlertStatus.RED;

        String msg = "";

        environmentSystem.playSound("RedAlertSound");

        int strength = shieldSystem.raiseShields(shieldFrequency);

        msg += String.format("Shield Strength %d%%", strength);

        return msg;
    }
    

}

This first test only ensures the alert sound is played:

void "execute red alert causes alert sound to be played"() {
    given:
    EnvironmentSystem environmentSystem = Mock(EnvironmentSystem)
    CommandComputer commandComputer = new CommandComputer()
    commandComputer.setEnvironmentSystem(environmentSystem)

    when:
    commandComputer.execute("Red Alert")

    then:
    1 * environmentSystem.playSound("RedAlertSound")

}

Spock mocks behave similarly to their Java counterparts, but the syntax for the validation is more fluent, concise, and can be very powerful (especially for complicated cases). Here, the test ensures the playSound method is invoked exactly once with the parameter "RedAlertSound". The 1 * can be substituted for the number of required invocations. If the sound should be played twice use 2 * environmentSystem.playSound("RedAlertSound")

Returning Values from Mocks

Often mock methods will need to return a value. It is a feature supported by all the mocking frameworks, but again Spock's syntax is much more fluent and concise.

void "execute red alert causes shields to be raised"() {
    given:
    EnvironmentSystem environmentSystem = Mock(EnvironmentSystem)
    ShieldSystem shieldSystem = Mock(ShieldSystem)

    CommandComputer commandComputer = new CommandComputer()
    commandComputer.setEnvironmentSystem(environmentSystem)
    commandComputer.setShieldSystem(shieldSystem)
    commandComputer.setShieldFrequency(1000)

    when:
    String result = commandComputer.execute("Red Alert")

    then:
    1 * environmentSystem.playSound("RedAlertSound")
    1 * shieldSystem.raiseShields(1000) >> 100

    result.contains("Shield Strength 100%")
}

The operator >> is used to specify the return value. In this example, the shieldSystem mock returns 100 when the raiseShields(1000) method is invoked. By default Spock will return null for mock methods.

Using Closures for Return Values and Parameters

Validating interactions can be very complicated. In many cases the parameter passed to a method is a complicated object graph, or isn't known until runtime.

For example, take the scenario from the previous section where the command computer is updated with data from the sensor system. Imagine testing the sensor system with a mock CommandComputer instance. How would the SensorReading parameter be validated when the updateFromSensors() method is called?

With traditional Java mocking frameworks, there are two choices. One way would be to develop a custom class that handles checking the parameter (i.e. a matcher). The other is a process called argument capture. The mock stores (or captures) the parameter when the method is invoked, allowing the test to validate it afterwards.

The return values can become just as complicated. There are times the return value depends on the parameter used. For example, imagine mocking a message factory that retrieves a message from a resource bundle based on a message key passed in. The factory may be used multiple times in a test. Each time the mock has to return the correct message for a given key.

Spock, since it's based on Groovy, has a significant advantage over the other Java based mock frameworks: closures. Closures can be used for any parameter or the return value.

For example, suppose the shield frequency was based on a calculation that was difficult to mock. How do you test the shield system interaction?

With traditional mocking frameworks, the only option would be to use an argument capture. With Mockito it would look like:

@Test
public void commandComputerPassesInShieldFrequencyWhenExecutingRedAlert() {
    CommandComputer commandComputer = new CommandComputer();

    ShieldSystem shieldSystem = mock(ShieldSystem.class);
    commandComputer.setShieldSystem(shieldSystem);

    EnvironmentSystem environmentSystem = mock(EnvironmentSystem.class);
    commandComputer.setEnvironmentSystem(environmentSystem);

    commandComputer.execute("Red Alert");

    ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
    verify(shieldSystem).raiseShields(argument.capture());

    assertThat(argument.getValue(), is(commandComputer.getShieldFrequency()));
}

In Spock, it would simply be:

void "execute red alert causes shields to be raised with the correct shield frequency"() {
    given:
    EnvironmentSystem environmentSystem = Mock(EnvironmentSystem)
    ShieldSystem shieldSystem = Mock(ShieldSystem)

    CommandComputer commandComputer = new CommandComputer()
    commandComputer.setEnvironmentSystem(environmentSystem)
    commandComputer.setShieldSystem(shieldSystem)

    when:
    String result = commandComputer.execute("Red Alert")

    then:
    
    1 * shieldSystem.raiseShields({int shieldFrequency ->
        shieldFrequency == commandComputer.getShieldFrequency()
    }) >> 100
    
    result.contains("Shield Strength 100%")
}

It's a much simpler syntax, and the closure can even be shortened to:

1 * shieldSystem.raiseShields({it == commandComputer.getShieldFrequency()}) >> 100

The closure is expected to evaluate to a boolean: true if the argument matches, false otherwise.

Closures can also be used for return values. As an (admittedly contrived) example, suppose the test called for the shield system to return lower shield values for higher shield frequency. If the shield frequency is greater than 1000, then it should return a value of 90%. Otherwise, return 100%.

Again it is possible with traditional mocking frameworks, but is a bit cumbersome. For example, in Mockito it would be:

@Test
public void commandComputerHandlesLowerShieldValuesWhenFrequencyIsHigh() {
    CommandComputer commandComputer = new CommandComputer();

    ShieldSystem shieldSystem = mock(ShieldSystem.class);
    commandComputer.setShieldSystem(shieldSystem);

    EnvironmentSystem environmentSystem = mock(EnvironmentSystem.class);
    commandComputer.setEnvironmentSystem(environmentSystem);

    commandComputer.setShieldFrequency(1100);

    when(shieldSystem.raiseShields(1100)).thenAnswer(new Answer<Integer>() {
        public Integer answer(InvocationOnMock invocationOnMock) throws Throwable {
            Integer shieldFrequency = (Integer) invocationOnMock.getArguments()[0];
            if(shieldFrequency > 1000) {
                return 90;
            }
            else {
                return 100;
            }
        }
    });

    String result = commandComputer.execute("Red Alert");

    assertThat(result, is("Shield Strength 90%"));
}

In Spock, it is:

void "execute red alert causes shields to be raised with shield strength based on frequency"() {
    given:
    EnvironmentSystem environmentSystem = Mock(EnvironmentSystem)
    ShieldSystem shieldSystem = Mock(ShieldSystem)

    CommandComputer commandComputer = new CommandComputer()
    commandComputer.setEnvironmentSystem(environmentSystem)
    commandComputer.setShieldSystem(shieldSystem)
    commandComputer.setShieldFrequency(1100)

    when:
    String result = commandComputer.execute("Red Alert")

    then:
    
    1 * shieldSystem.raiseShields(1100) >>  {int shieldFrequency ->
        if(shieldFrequency > 1000) {
            return 90
        }
        else {
            return 100
        }
    }
    

    result.contains("Shield Strength 90%")
}

Conclusion

These examples barely scratch the surface of Spock. There are more blocks, such as expect. Interactions allow parameter constraints like not null and wildcards. There are more annotations (@Ignore and others). There is integration with Grails, Gradle, Spring, and Tapestry. All of this before the 1.0 release.

There have been many ideas and frameworks that have revolutionized testing in the last decade. Spock brings them all together in one framework, and has a simple, easy to use API. It makes testing easier then ever before. Check out the Spock wiki for many more features and examples.

More Info

Spock Wiki http://code.google.com/p/spock/w/list
Groovy http://groovy.codehaus.org/Documentation
Maven http://maven.apache.org/guides/
GMaven http://docs.codehaus.org/display/GMAVEN/Home

Valid XHTML 1.0 Strict [Valid RSS]
RSS
Top