Spock Framework

Patrick van Dissel

Spock is a testing and specification framework for Java and Groovy applications. What makes it stand out from the crowd is its beautiful and highly expressive specification language.
http://docs.spockframework.org
— Spock Framework Reference Documentation
Thanks to its JUnit runner, Spock is compatible with most IDEs, build tools, and continuous integration servers. Spock is inspired from JUnit, jMock, RSpec, Groovy, Scala, Vulcans, and other fascinating life forms.
http://docs.spockframework.org
— Spock Framework Reference Documentation
def 'A new stack is empty'() {
    def stack = new Stack()

    expect:
    stack.empty
}
def 'Can push an element onto the stack'() {
    given: 'a new stack and element'
    def stack = new Stack()
    def element = 'push me'

    when: 'an element is pushed'
    stack.push(element)

    then: 'the stack has that element'
    !stack.empty
    stack.size() == 1
    stack.peek() == element
}
def 'Pop from an empty stack throws EmptyStackException'() {
    def stack = new Stack()

    when:
    stack.pop()

    then:
    thrown(EmptyStackException)
    stack.empty
}
def '''Pop from an empty stack throws EmptyStackException
       without a message'''() {
    def stack = new Stack()

    when:
    stack.pop()

    then:
    def e = thrown(EmptyStackException)
    !e.message
    stack.empty
}
def 'Events are published to all subscribers'() {
    def subscriber1 = Mock(Subscriber)
    def subscriber2 = Mock(Subscriber)
    def publisher = new Publisher()
    publisher.add(subscriber1)
    publisher.add(subscriber2)

    when:
    publisher.fire("event")

    then:
    1 * subscriber1.receive("event")
    1 * subscriber2.receive(_ as String)
}
@Unroll
def 'The maximum of the numbers [#a] and [#b] is [#c]'() {
    expect:
    Math.max(a, b) == c

    where:
    a | b || c
    5 | 1 || 5
    3 | 9 || 9
}
def 'Adding an item to a list increases the size by one'() {
    given: 'a list with one item'
    def names = ['Spock']

    when: 'adding one item to the list'
    names << 'Groovy'

    then: 'the list size increases by one'
    names.size() == old(names.size()) + 1
}
def 'Subscriber thrown exception does not impact publisher'() {
    def subscriber1 = Mock(Subscriber)
    def subscriber2 = Mock(Subscriber)
    def publisher = new Publisher()
    publisher.add(subscriber1)
    publisher.add(subscriber1)
    publisher.add(subscriber2)

    when:
    publisher.fire('event')

    then:
    1 * subscriber1.receive('event')
    1 * subscriber1.receive(_) >> {
        throw new RuntimeException()
    }
    1 * subscriber2.receive(_ as String)
}

Spock Primer

Characteristics

  • GroovyDSL

  • Sputnik, Spock’s JUnit runner

  • Works for Java and Groovy code

Characteristics

  • Specification-based
    (Specification by Example? BDD?)

  • Data Driven Testing
    with data providers and tables

  • Interaction Based Testing
    with build-in support for
    mocking, stubbing and spying

Imports

import spock.lang.*

Specification

import spock.lang.Specification

class MyFirstSpec extends Specification {
    // fields
    // fixture methods
    // feature methods
    // helper methods
}

Instance Fields

class InstanceFields extends Specification {
    def obj = new ClassUnderSpecification()
    def coll = new Collaborator()
}

Shared Fields

class SharedFields extends Specification {
    @Shared
    def res = new VeryExpensiveResource()
}

Static Fields

class StaticFields extends Specification {
    static final PI = 3.141592654
}

Fixture Methods

class FixtureMethods extends Specification {
    def setup() {}        // run before every feature method
    def cleanup() {}      // run after every feature method

    def setupSpec() {}    // run before the first feature method
    def cleanupSpec() {}  // run after the last feature method
}

Feature Methods

class FeatureMethods extends Specification {
    def 'pushing an element on the stack'() {
        // blocks go here
    }
}

Feature Methods

Phases
  1. Set up the feature’s fixture

  2. Provide a stimulus to the system under specification

  3. Describe the response expected from the system

  4. Clean up the feature’s fixture

Blocks

Blocks2Phases

Setup Blocks

setup:
def stack = new Stack()
given:
def stack = new Stack()
def stack = new Stack()

When and Then Blocks

when:   // stimulus
then:   // response

Conditions

def 'Can push an element onto the stack'() {
    given: 'a new stack and element'
    def stack = new Stack()
    def element = 'push me'

    when: 'an element is pushed'
    stack.push(element)

    then: 'the stack has that element'
    !stack.empty
    stack.size() == 1
    stack.peek() == element
}

Condition Feedback

Condition not satisfied:

stack.size() == 2
|     |      |
|     1      false
[push me]

Exception Conditions

def '''Pop from an empty stack throws EmptyStackException
       without a message'''() {
    def stack = new Stack()

    when:
    stack.pop()

    then:
    def e = thrown(EmptyStackException)
    !e.message
    stack.empty
}

Expect Blocks

expect:
Math.max(1, 2) == 2

vs

when:
def x = Math.max(1, 2)

then:
x == 2

Helper Methods

def 'Offered PC matches preferred configuration'() {
    when:
    def pc = shop.buyPc()

    then:
    matchesPreferredConfiguration(pc)
}

void matchesPreferredConfiguration(PC pc) {
    assert pc.vendor == 'Sunny'
    assert pc.clockRate >= 2333
    assert pc.ram >= 4096
    assert pc.os == 'Linux'
}

Specifications as Documentation

def 'Adding an item to a list increases the size by one'() {
    given: 'a list with one item'
    def names = ['Spock']

    when: 'adding one item to the list'
    names << 'Groovy'

    then: 'the list size increases by one'
    names.size() == old(names.size()) + 1
}

BDD-style Reports

spock-reports extension for BDD-style reports

Data Driven Testing

@Unroll
def 'The maximum of the numbers [#a] and [#b] is [#c]'() {
    expect:
    Math.max(a, b) == c

    where:
    a | b || c
    5 | 1 || 5
    3 | 9 || 9
}

Interaction Based Testing

def 'Events are published to all subscribers'() {
    def subscriber1 = Mock(Subscriber)
    def subscriber2 = Mock(Subscriber)
    def publisher = new Publisher()
    publisher.add(subscriber1)
    publisher.add(subscriber2)

    when:
    publisher.fire("event")

    then:
    1 * subscriber1.receive("event")
    1 * subscriber2.receive(_ as String)
}

Extensions

Ignore / Require

@spock.lang.Ignore("Reason")
def "my feature"() { ... }
@spock.lang.IgnoreRest
def "I'll run"() { ... }
@spock.lang.IgnoreIf({ os.windows })
def "I'll run everywhere but on Windows"() { ... }
@spock.lang.Requires({ os.windows })
def "I'll only run on Windows"() { ... }

Timeout

To fail a feature method, fixture, or class
that exceeds a given execution duration,
use spock.lang.Timeout

@Timeout(10)
class TimedSpec extends Specification {
    def "I fail after ten seconds"() { ... }
    def "Me too"() { ... }

    @Timeout(value = 250, unit = TimeUnit.MILLISECONDS)
    def "I fail much faster"() { ... }
}

Stepwise

To execute features in the order that they are declared,
use spock.lang.Stepwise

@Stepwise
class RunInOrderSpec extends Specification {
    def "I run first"()  { ... }
    def "I run second"() { ... }
}

Title and Narrative

To attach a natural-language name to a spec,
use spock.lang.Title

@Title("This is easy to read")
class ThisIsHarderToReadSpec { ... }

To attach a natural-language description to a spec,
use spock.lang.Narrative

@Narrative(""""
As a user
I want foo
So that bar
""")
class GiveTheUserFooSpec { ... }

Subject

To indicate one or more subjects of a spec,
use spock.lang.Subject

@Subject([Foo, Bar])
class MySpec { ... }

Subject can be applied to fields and local variables

@Subject
Foo myFoo

Issue

To indicate that a feature or spec
relates to one or more issues in an external tracking system,
use spock.lang.Issue

@Issue("http://my.issues.org/FOO-1")
class MySpec {
    @Issue("http://my.issues.org/FOO-2")
    def "Foo should do bar"() { ... }

    @Issue(["http://my.issues.org/FOO-3",
            "http://my.issues.org/FOO-4"])
    def "I have two related issues"() { ... }
}

Use

To activate one or more Groovy categories
within the scope of a feature method or spec,
use spock.util.mop.Use

class ListExtensions {
    static avg(List list) { list.sum() / list.size() }
}

class MySpec extends Specification {
    @Use(listExtensions)
    def "can use avg() method"() {
        expect:
        [1, 2, 3].avg() == 2
    }
}

ConfineMetaClassChanges

To confine meta class changes to the scope of a feature method or spec class,
use spock.util.mop.ConfineMetaClassChanges

@Stepwise
class FooSpec extends Specification {
    @ConfineMetaClassChanges
    def "I run first"() {
        when:
        String.metaClass.someMethod = { delegate }

        then:
        String.metaClass.hasMetaMethod('someMethod')
    }

    def "I run second"() {
        when:
        "Foo".someMethod()

        then:
        thrown(MissingMethodException)
    }
}

AutoCleanup

Automatically clean up a field or property
at the end of its lifetime
by using spock.lang.AutoCleanup

@AutoCleanup('dispose')
@AutoCleanup(quiet = true)

Comparison to JUnit

SpockJUnit

Specification

Test class

setup()

@Before

cleanup()

@After

setupSpec()

@BeforeClass

cleanupSpec()

@AfterClass

SpockJUnit

Feature

Test

Feature method

Test method

Data-driven feature

Theory

Condition

Assertion

Exception condition

@Test(expected=…​)

Interaction

Mock expectation (e.g. in Mockito)

Why Spock?

Easy to learn

If you know Java and JUnit, you are almost ready to go

Powered by Groovy

Java’s dynamic companion lets you do more in less time

Eliminates waste

No assertion API. No record/replay mocking API. No superfluous annotations. Everything is questioned, and only the essential is kept

Detailed information

Spock' runtime collects a wealth of information, and presents it to you when needed

Designed for use

It’s designed from a user’s perspective

Open-minded

Test-first? Test-last? Unit-level? Integration-level? TDD? BDD?
It tries to give you the flexibility to do it your way

Beautiful language

Express your thoughts in a beautiful and highly expressive specification language

Extensible for everyone

With Spock’s interception-based extension mechanism, you can easily create your own extensions

Compatible with JUnit

Leverage JUnit’s reporting capabilities

Learns from the history

Combines the best features of proven tools like JUnit, jMock, and RSpec, and innovates on top of them

Resources

Thanks