Test Long & Prosper

Spock Testing Workshop

Spock?

Groovy Testing

Framework/DSL

Star Trek?

Specification & Mock

Groovy = very close to Java optionally typed language

Project Setup
with 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">

    <!-- … -->

    <dependencies>

        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.5.8</version>
            <scope>test</scope>
            <type>pom</type>
        </dependency>

        <dependency>
            <groupId>org.spockframework</groupId>
            <artifactId>spock-core</artifactId>
            <version>1.3-groovy-2.5</version>
            <scope>test</scope>
        </dependency>

        <!-- Byte Buddy enables mocking of classes (in addition to interfaces)
             see http://spockframework.org/spock/docs/1.3/all_in_one.html#_mocking_classes -->
        <dependency>
            <groupId>net.bytebuddy</groupId>
            <artifactId>byte-buddy</artifactId>
            <version>1.10.3</version>
            <scope>test</scope>
        </dependency>

        <!-- Objenesis enables mocking of classes without default constructor (together with CGLib)
             see http://spockframework.org/spock/docs/1.3/all_in_one.html#_mocking_classes -->
        <dependency>
            <groupId>org.objenesis</groupId>
            <artifactId>objenesis</artifactId>
            <version>3.1</version>
            <scope>test</scope>
        </dependency>

        <!-- … -->

    </dependencies>

Explicit Groovy version

Spock dependency

Ability to mock non-interfaces

    <build>
        <sourceDirectory>src/main/java</sourceDirectory>
        <testSourceDirectory>src/test/groovy</testSourceDirectory>

        <plugins>
            <!-- Configure GMavenPlus to compile Groovy test sources
                 see https://github.com/groovy/GMavenPlus/wiki/Usage -->
            <plugin>
                <groupId>org.codehaus.gmavenplus</groupId>
                <artifactId>gmavenplus-plugin</artifactId>
                <version>1.8.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>addTestSources</goal>
                            <goal>compileTests</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>

            <!-- Spock tests end with "Spec" -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M4</version>
                <configuration>
                    <includes>
                        <include>**/*Test.*</include>
                        <include>**/*Spec.*</include>
                    </includes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Use GMavenPlus to compile Groovy test code

Make Surefire find *Spec files

(not only *Test)

Project Setup
with Gradle

plugins {
    id 'java'
    id 'groovy'
}

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    testImplementation 'org.codehaus.groovy:groovy-all:2.5.8'
    testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5'
    testImplementation 'net.bytebuddy:byte-buddy:1.10.3'
    testImplementation 'org.objenesis:objenesis:3.1'
    testImplementation 'ch.qos.logback:logback-classic:1.2.3'
    compileOnly 'org.slf4j:slf4j-api:1.7.29'
}

sourceCompatibility = '11'

Groovy plugin

Spock &
Groovy dependencies

Ability to mock
non-interfaces

Specifications,

Features,

Fixtures,

Blocks

class MySpec extends Specification {

    User user = createUser(LocalDate.now().minusYears(23))
    
    @Shared JsonSlurper jsonSlurper = new JsonSlurper()
    
    static final PI = 3.141592654

    def setupSpec() { /* before first feature method */ }
    def setup() { /* before each feature method */ }
    def cleanup() { /* after each feature method */ }
    def cleanupSpec() { /* after last feature method */ }

    def "isOfAge returns true"() {
    	expect:
        !user.isOfAge()
    }
    
    boolean createUser(LocalDate age) {
        return new User(LocalDate.now().minusYears(17))
    }
}

extend spock.lang.Specification

field initialization before each feature

fixture methods

feature method

shared field initialization only once per spec

static field for constants only

helper method

SuperSpec
SubSpec
setup()
setupSpec()
cleanup()
setup()
"feature"()
setupSpec()
cleanup()
cleanupSpec()
cleanupSpec()
class SuperSpec extends Specification {
    
    def setupSpec() { println "setup super spec" }
    def setup() { println "setup super" }
    def cleanup() { println "cleanup super" }
    def cleanupSpec() { println "cleanup super spec" }
}

class SubSpec extends SuperSpec {
    
    def setupSpec() { println "setup sub spec" }
    def setup() { println "setup sub" }
    def "feature"() {
        expect:
        true
    }
    def cleanup() { println "cleanup sub" }
    def cleanupSpec() { println "cleanup sub spec" }
}

1

2

3

4

5

6

8

7

9

given
when
then
expect

Setup

Stimulous

Response

Cleanup

cleanup

Phase

Block

\overbrace{\quad\quad\quad\quad}^\text{}
\overbrace{\quad\quad\quad\quad\quad\quad\quad\quad}^\text{}
\overbrace{\quad\quad\quad\quad}^\text{}
\overbrace{\quad\quad\quad\quad\quad\quad\quad\quad}^\text{}

optional

define variables

no semantic

requires then

single line best

combine when/then

conditions only!

every line

optional

feature specific cleanup

class MySpec extends Specification {

    def "given, when, then"() {
        given:
        User user = new User("jdoe", "John", "Doe", LocalDate.now().plusYears(18))
        
        when:
        boolean ofAge = user.isOfAge()
        
        then:
        ofAge
    }

    def "when, then"() {
        when:
        boolean ofAge = new User("jdoe", "John", "Doe", LocalDate.now().plusYears(18)).isOfAge()
        
        then:
        ofAge
    }
    
    def "given, expect"() {
        given:
        User user = new User("jdoe", "John", "Doe", LocalDate.now().plusYears(18))
        
    	expect:
        user.isOfAge()
    }
    
    def "expect"() {
    	expect:
        new User("jdoe", "John", "Doe", LocalDate.now().plusYears(18)).isOfAge()
    }
}

Let's code ;)

→ https://github.com/mkutz/spock-testing-workshop/#part-1-blocks-expectations-and-pending-features

Data-Driven Testing

class MathSpec extends Specification {
    def "maximum of two numbers"() {
        expect:
        Math.max(1, 3) == 3
        Math.max(7, 4) == 7
        Math.max(0, 0) == 0
    }
}
class MathSpec extends Specification {
    def "maximum of two numbers"(int a, int b, int c) {
        expect:
        Math.max(a, b) == c

        where:
        a | b | c
        1 | 3 | 3
        7 | 4 | 7
        0 | 0 | 0
    }
}

Mocking & Stubbing

Spock Testing Workshop

By Michael Kutz

Spock Testing Workshop

  • 3,279