Introduction

name with icon light

Spockk is an add-on for the Spock testing framework that aims to provide Kotlin-native version of Spock’s expressive specification syntax. Spockk is not a standalone testing framework and must be used in combination with Spock. Spockk used together with Spock’s JUnit Platform test engine is compatible with most build tools and IDEs.

Getting Started

To try Spockk in your local environment, clone the Spockk Example Project.

Prerequisites

  • JDK 21 or newer

  • Kotlin 2.0.0 or newer

Configuration

Spockk consists of four core modules that must be applied to your project to start using its expressive specification syntax.

The Spockk Core module defines the specification syntax that allows definining Spock-like specifications in Kotlin.

The Spock Core module provides a TestEngine implementation that allows Spockk tests to run on the JUnit Platform.

Enable the Spockk test engine in your Gradle project
// build.gradle.kts

dependencies {
    testImplementation("org.spockframework:spock-core:2.4-groovy-5.0")
    testImplementation("io.github.pshevche.spockk:spockk-core:0.3.2")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.0.3")
}

tasks.named<Test>("test") {
    useJUnitPlatform()
}

The Spockk Gradle plugin applies a Kotlin compiler plugin that transforms Spockk’s concise specification syntax into runnable tests during compilation.

Apply the Spockk Gradle plugin to support specification syntax
// build.gradle.kts

plugins {
    id("io.github.pshevche.spockk") version "0.3.2"
}

Finally, for a smooth development experience in JetBrains IDEs, install the Spockk IntelliJ plugin from the Marketplace. The plugin recognizes Spockk syntax in your tests and enables you to execute them directly from the IDE via Gradle.

Writing Tests

Building blocks

Similarly to Spock, Spockk lets you define specifications that describe expected features of the system under specification. A specification is represented as a Kotlin class, while features are defined as its methods.

import spock.lang.Specification

class MyFirstSpecification : Specification() {
    fun `adding an element to a list`() {
        // define preconditions, actions, and conditions
    }
}

Spockk expects features to be structured into so-called blocks. The framework provides built-in elements that help you define such blocks using the Behavior-Driven Development style. Currently, the framework supports the following types of blocks:

  • setup / given: defines feature setup and preconditions;

  • when: defines actions applied to the system under specification;

  • then: declares the expected state of the fixture after applying actions;

  • expect: defines a standalone condition block;

  • cleanup: defines cleanup logic that always runs, even if the feature fails;

  • and: allows combining multiple precondition, action, and condition blocks.

A feature method must have at least one block to be eligible for execution. A block consists of all statements between two block labels, or between a block label and the end of the feature.

import io.github.pshevche.spockk.lang.and
import io.github.pshevche.spockk.lang.given
import io.github.pshevche.spockk.lang.`when`
import io.github.pshevche.spockk.lang.then

import spock.lang.Specification

class MyFirstSpecification : Specification() {
    fun `adding an element to a list`() {
        given
        val myList = mutableListOf<Int>()

        `when`
        myList.add(1)

        and
        myList.add(2)

        then
        assert(myList.size == 2)
    }
}

Specifications as Documentation

Block labels can include natural-language descriptions, making your specifications more expressive and readable — even for non-engineers.

Block labels with descriptions
given("an empty bank account")
// ...

`when`("the account is credited $10")
// ...

then("the account's balance is $10")
// ...

Fixture Methods

The following section describes how Spock’s fixture methods can be used in Kotlin using the Spockk add-on. For general information about fixture methods and their runtime behavior, consult Spock’s reference documentation.

Lifecycle Methods

Spockk supports Spock’s fixture methods for managing test lifecycle:

import io.github.pshevche.spockk.lang.expect
import spock.lang.Specification

class DatabaseSpec : Specification() {
    fun setupSpec() {
        // runs once before the first feature method
    }

    fun setup() {
        // runs before every feature method
    }

    fun cleanup() {
        // runs after every feature method
    }

    fun cleanupSpec() {
        // runs once after all feature methods
    }

    fun `insert a record`() {
        expect
        // ...
    }
}

Cleanup Blocks

A cleanup block within a feature method is used to free any resources used by a feature method and is run even if the feature method has produced an exception. For general information about cleanup blocks and their runtime behavior, consult Spock’s reference documentation.

import io.github.pshevche.spockk.lang.cleanup
import io.github.pshevche.spockk.lang.given
import io.github.pshevche.spockk.lang.expect

import spock.lang.Specification

class ResourceSpec : Specification() {
    fun `read file contents`() {
        given
        val stream = openResourceStream()

        expect
        assert(stream.available() > 0)

        cleanup
        stream.close()
    }
}

Data Driven Testing

The following section describes how Spock’s data-driven features can be defined in Kotlin using the Spockk add-on. For general information about data-driven features and runtime behavior, consult Spock’s reference documentation.

Data Tables

Data tables are a convenient way to exercise a feature method with a fixed set of data values:

class MathSpec : Specification() {
  fun `maximum of two numbers`(a: Int, b: Int, c: Int) {
    expect
    assertEquals(c, max(a, b))

    where
    a ; b ; c
    1 ; 3 ; 3
    7 ; 4 ; 7
    0 ; 0 ; 0
  }
}

The first line of the table, called the table header, declares the data variables. The subsequent lines, called table rows, hold the corresponding values. For each row, the feature method will get executed once; we call this an iteration of the method. If an iteration fails, the remaining iterations will nevertheless be executed. All failures will be reported.

For a seamless formatting experience, install Spockk IntelliJ plugin and set the following ktlint configuration:

ktlint_standard_statement-wrapping = disabled
ktlint_standard_no-multi-spaces = disabled

Data tables must have at least two columns. A single-column table can be written as:

where
a ; `_`
1 ; `_`
7 ; `_`
0 ; `_`

Data Pipes

Data tables aren’t the only way to supply values to data variables. In fact, a data table is just syntactic sugar for one or more data pipes:

...
where
variable(a).from(1, 7, 0)
variables(b, c).from(listOf(3, 4, 0), listOf(3, 7, 0))

A data pipe, indicated by the variable(<feature_var>).from(<var_values>) construct, connects a data variable to a data provider. The data provider holds all values for the variable, one per iteration.

Accessing Other Data Variables

There are only two possibilities to access one data variable from the calculation of another data variable.

The first possibility are derived data variables where the variable is defined to have a single value and it is equal to another variable. Every data variable that is defined this way can access all previously defined data variables, including the ones defined through data tables or data pipes:

...
where
variable(a).from(1, 2, 3)
variable(b).from(a)

The second possibility is to access previous columns within data tables:

...
where
a ; b
3 ; a + 1
7 ; a + 2
0 ; a + 3

Combining Data Tables, Data Pipes, and Variable Assignments

Data tables, data pipes, and variable assignments can be combined as needed:

...
where
a ; b
1 ; a + 1
7 ; a + 2
0 ; a + 3

variable(c).from(3, 4, 0)
variable(d).from(c)

Limitations

Spockk is still in active development and currently lacks several features typically expected from an enterprise-level testing framework. For example, it does not yet provide built-in fixtures for interaction-based testing. Similarly, it does not yet provide built-in fixtures for defining conditions (assertions). However, it works seamlessly with Kotlin’s native assertion framework, as well as popular libraries such as AssertJ and Hamcrest. These and other enhancements are on the roadmap. Stay tuned for future releases!

Changelog

v0.3.2

  • [Core] Allow accessing @Shared fields from setupSpec and cleanupSpec methods

v0.3.1

  • [Core] Do not fail the build if the source set has no specifications to transform

v0.3.0

  • [Core] Added support for fixture methods (setup(), cleanup(), setupSpec(), cleanupSpec())

  • [Core] Added support for setup and cleanup block labels in feature methods

  • [Core] Fixed field behavior in fixture methods and data providers to match Spock semantics

  • [Core] Enabled reliable block information access for Spock extensions

  • [Core] Added support for Kotlin 2.3.20

  • [IntelliJ Plugin] Added syntax support for setup and cleanup block labels

  • [IntelliJ Plugin] Suppressed false-positive unreachable code warnings in cleanup and where blocks

v0.2.1

  • [Core] Fixed the transformation of extension functions in test fixtures

  • [IntelliJ Plugin] Maintenance release with dependency upgrades

v0.2.0

  • [Core] Added support for data-driven features

  • [Core] Introduced experimental support for Spock extensions

  • [Core] Added support for Kotlin 2.3.0

  • [IntelliJ Plugin] Added syntax support for Spock’s data-driven features

  • [IntelliJ Plugin] Integrated with Spock’s native IntelliJ plugin

v0.1.0

  • [Core] Implement rich BDD-style syntax for defining test features

  • [IntelliJ plugin] Detect Spockk syntax in test sources

  • [IntelliJ plugin] Execute Spockk tests with Gradle from IDE