Testing with Spock
Prerequisites: Classes and objects, Exceptions and error handling
Spock is a testing and specification framework for Java and Groovy applications. Its tests read like specifications, its data tables make parameterized tests trivial, and its failure messages show exactly what went wrong. Spock runs on the JUnit Platform, so Gradle, Maven, and IDEs run it like any JUnit test.
The examples in this lesson were run with Spock 2.4 on Groovy 5.1 (Spock
publishes one artifact per Groovy major version; at the time of writing
the latest was built for Groovy 5). The Groovy runner prints a one-line
JUnit summary; the time= value varies from run to run.
A first specification
// illustrative — run with Spock on the classpath
import spock.lang.Specification
class StackSpec extends Specification {
def "push adds an element to the top"() {
given:
def stack = new ArrayDeque<String>()
when:
stack.push('a')
stack.push('b')
then:
stack.size() == 2
stack.peek() == 'b'
}
def "pop on an empty stack throws"() {
given:
def stack = new ArrayDeque<String>()
when:
stack.pop()
then:
thrown(NoSuchElementException)
}
}
JUnit5 launcher: passed=2, aborted=0, failed=0, skipped=0, time=132ms
- A spec extends
Specification; each feature method has a string name. - Blocks structure the test:
given:(setup),when:(action),then:(expectations),expect:(action and check in one),where:(data),cleanup:. - Every line in
then:andexpect:is an implicit assertion — noassertkeyword needed.
Data-driven tests
// illustrative — run with Spock on the classpath
import spock.lang.Specification
class MathSpec extends Specification {
def "max of #a and #b is #c"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3
7 | 4 || 7
0 | 0 || 0
2 | 2 || 5
}
}
JUnit5 launcher: passed=4, aborted=0, failed=1, skipped=0, time=159ms
Failures (1):
Spock:MathSpec:max of #a and #b is #c:max of 2 and 2 is 5
MethodSource [className = 'MathSpec', methodName = 'max of #a and #b is #c', methodParameterTypes = 'java.lang.Object, java.lang.Object, java.lang.Object']
=> Condition not satisfied:
Math.max(a, b) == c
| | | | | |
| 2 2 2 | 5
| false
class java.lang.Math
The where: block is a data table: each row runs as its own test,
and #a in the name is replaced with the row's values. The deliberately
wrong last row shows Spock's power assertion output: the value of
every sub-expression. (The summary counts the parent feature as well as
its four rows, and the output continues with a stack trace, trimmed
here.)
Mocks and stubs
// illustrative — run with Spock on the classpath
import spock.lang.Specification
class CheckoutSpec extends Specification {
def "charges the card with the cart total"() {
given:
def payments = Mock(PaymentGateway)
def checkout = new Checkout(payments: payments)
when:
def ok = checkout.pay([apple: 0.5, bread: 2.25], 'card-1')
then:
1 * payments.charge('card-1', 2.75) >> true
0 * payments.refund(_)
ok
}
def "reports a declined card"() {
given:
def payments = Stub(PaymentGateway) { charge(_, _) >> false }
expect:
!new Checkout(payments: payments).pay([tea: 3], 'card-2')
}
}
interface PaymentGateway {
boolean charge(String card, BigDecimal amount)
void refund(String card)
}
class Checkout {
PaymentGateway payments
boolean pay(Map<String, BigDecimal> items, String card) {
payments.charge(card, items.values().sum() as BigDecimal)
}
}
JUnit5 launcher: passed=2, aborted=0, failed=0, skipped=0, time=290ms
Mock(Type)creates a mock;1 * payments.charge('card-1', 2.75)says "expect exactly one call with these arguments", and>> truegives its return value._matches any argument;0 * ...asserts a call never happens.Stub(Type)only returns values and never fails on interactions.
Useful extras
@Unrollis the default in Spock 2, so every data row is reported separately.setup(),cleanup(),setupSpec(), andcleanupSpec()run around features;@Sharedfields are shared between them.@Ignore,@IgnoreIf({ os.windows }),@Timeout(5), and@Retrycontrol execution.with(object) { name == 'Ada'; age == 36 }checks several properties at once, andverifyAll { }reports every failed condition instead of stopping at the first.
Setting up Spock in Gradle
// illustrative — build.gradle
dependencies {
testImplementation platform('org.spockframework:spock-bom:2.4-groovy-5.0')
testImplementation 'org.spockframework:spock-core'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') { useJUnitPlatform() }
Specs go in src/test/groovy and are named *Spec.groovy. Spock works
just as well for testing Java code — many Java teams use it for that
reason alone.
Without Spock: JUnit and plain assert
Groovy also runs JUnit 5/6 test classes directly (groovy MyTest.groovy
uses the bundled launcher), and for small scripts, power-asserting with
assert is often all you need.