Overview

This guide assumes that you have a basic knowledge of Kotlin. If you have a Ruby background and are familiar with RSpec, you won’t have any issues to get up and running.

What is Spek?

Tests are not only to check the code you’ve written executes and works, but also that it works as it should, that is, that as a developer what’s been implemented matches the requirements.

A green test doesn’t tell you that the code correctly implements the specification. The user does, once they try it. However, we can try and make sure we’ve correctly understood the specifications by contrasting the code with the specs. And what better place to have these specs to lookup than in the same place we write the test code?

That’s what Spek does. It is a specification framework that allows you to easily define specifications in a clear, understandable, human readable way.

You call them tests, we call them specifications.

Why Kotlin?

Kotlin is statically typed OSS language developed and sponsored by JetBrains. It offers many advantages over Java such as its conciseness, expressiveness and the ability to create nice DSL. It’s 100% compatible with Java which means you can call Java from Kotlin and Kotlin from Java. And it’s also easy to pick up.

Spek is written in Kotlin and specifications you write will be written in Kotlin. However, your SUT (System under Test) can be Java or Kotlin. As such you can easily use Kotlin for your specifications. Yes, you call them tests. Start calling them specifications.

Supported Java Versions

Current version requires JDK 8 (as a requirement for JUnit 5).

Android

Android integration tests is currently not supported and you have to use the Jack toolchain to be able to build against Java 8.

IDE Support

Important
If you’re using the JUnit 4 runner, the plugin won’t work as expected due to the bundled JUnit plugin taking over.

Setting up

Currently, Spek is implemented as a JUnit Platform TestEngine.

You will need to include org.jetbrains.spek:spek-api and org.jetbrains.spek:spek-junit-platform-engine in your test classpath. The former is needed during compilation, while the latter during runtime only.

Note
For Android projects check Using Spek with JUnit 4 section.

Gradle

JUnit Platform provides a gradle plugin.

build.gradle
// setup the plugin
buildscript {
    dependencies {
        classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-M4'
    }
}

apply plugin: 'org.junit.platform.gradle.plugin'

junitPlatform {
    filters {
        engines {
            include 'spek'
        }
    }
}

repositories {
    maven { url "http://dl.bintray.com/jetbrains/spek" }
}

// setup dependencies
dependencies {
    testCompile 'org.jetbrains.spek:spek-api:1.1.2'
    testRuntime 'org.jetbrains.spek:spek-junit-platform-engine:1.1.2'
}

Gradle Kotlin Script

build.gradle.kts
import org.gradle.api.plugins.ExtensionAware

import org.junit.platform.gradle.plugin.FiltersExtension
import org.junit.platform.gradle.plugin.EnginesExtension
import org.junit.platform.gradle.plugin.JUnitPlatformExtension

// setup the plugin
buildscript {
    dependencies {
        classpath("org.junit.platform:junit-platform-gradle-plugin:1.0.0-M4")
    }
}

apply {
    plugin("org.junit.platform.gradle.plugin")
}

configure {
    filters {
        engines {
            include("spek")
        }
    }
}

// setup dependencies
dependencies {
    testCompile("org.jetbrains.spek:spek-api:1.1.2")
    testRuntime("org.jetbrains.spek:spek-junit-platform-engine:1.1.2")
}

// extension for configuration
fun JUnitPlatformExtension.filters(setup: FiltersExtension.() -> Unit) {
    when (this) {
        is ExtensionAware -> extensions.getByType(FiltersExtension::class.java).setup()
        else -> throw Exception("${this::class} must be an instance of ExtensionAware")
    }
}
fun FiltersExtension.engines(setup: EnginesExtension.() -> Unit) {
    when (this) {
        is ExtensionAware -> extensions.getByType(EnginesExtension::class.java).setup()
        else -> throw Exception("${this::class} must be an instance of ExtensionAware")
    }
}

Using later versions of Kotlin

If you are using a version of Kotlin that is different than the one used by Spek, you will want to exclude the group in your dependency definition.

build.gradle
dependencies {

    // your application's newer version of Kotlin
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"

    testCompile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
    testCompile ('org.jetbrains.spek:spek-api:1.1.2') {
        exclude group: 'org.jetbrains.kotlin'
    }
    testRuntime ('org.jetbrains.spek:spek-junit-platform-engine:1.1.2') {
        exclude group: 'org.junit.platform'
        exclude group: 'org.jetbrains.kotlin'
    }
}

Maven

JUnit provides support for Maven.

pom.xml
...
<dependency>
  <groupId>org.jetbrains.spek</groupId>
  <artifactId>spek-api</artifactId>
  <version>1.1.2</version>
  <type>pom</type>
</dependency>

Using Spek with JUnit 4

JUnit 4 based runner is still supported via JUnit Platform. You will need the following dependencies on the test classpath:

  • org.jetbrains.spek:spek-api:1.1.2

  • org.jetbrains.spek:spek-junit-platform-engine:1.1.2

  • org.junit.platform:junit-platform-runner:1.0.0-M4

The finally, annotate your specs with @RunWith(JUnitPlatform::class).

CalculatorSpec.kt
@RunWith(JUnitPlatform::class)
class CalculatorSpec: Spek({
    ...
})
Important
As mentioned in the IDE Support section, the IDEA plugin won’t work if you’re using the JUnit 4 runner.

Writing Specifications

Styles

It is important to know that Spek allows two styles of tests:

  • given, on, it

  • describe it

given, on, it

This is the classic Spek style and what the first version of Spek was based on. The idea is quite straightforward:

A test consists of four components:

  • name: which is the class name, indicating what exactly it is we’re testing

  • given: which defines the context, that is, the conditions under which I’m testing. Can be repeated any number of times for each name

  • on: which defines the action, that is, what is executing for the test to later verify. Can be repeated any number of times for each given.

  • it: which defines the actual tests, that is, what to be verified once the action is executed. Can be repeated any number of times for each on.

In the case of a calculator, this would be:

SimpleSpec.kt
object CalculatorSpec: Spek({
    given("a calculator") {
        val calculator = SampleCalculator()
        on("addition") {
            val sum = calculator.sum(2, 4)
            it("should return the result of adding the first number to the second number") {
                assertEquals(6, sum)
            }
        }
        on("subtraction") {
            val subtract = calculator.subtract(4, 2)
            it("should return the result of subtracting the second number from the first number") {
                assertEquals(2, subtract)
            }
        }
    }
})

Notice that both in this style, as the one below, the entire test is described inside of the Spek constructor. Each specification needs to inherit from the class Spek defined in the org.jetbrains.api package.

describe, it

The style follows the Jasmine and Mocha style frameworks, where things are defined in describe blocks.

A test consists of a minimum of three parts:

  • name: which is the class name, indicating what exactly it is we’re testing

  • describe: which defines the context of what we’re testing. We can nest as many describes as we want. Each one should provide more context

  • it: which defines the actual tests, that is, what to be verified once the action is executed. Can be repeated any number of times for each describe

SimpleSpec.kt
object SimpleSpec: Spek({
    describe("a calculator") {
        val calculator = SampleCalculator()

        on("addition") {
            val sum = calculator.sum(2, 4)

            it("should return the result of adding the first number to the second number") {
                assertEquals(6, sum)
            }
        }

        on("subtraction") {
            val subtract = calculator.subtract(4, 2)

            it("should return the result of subtracting the second number from the first number") {
                assertEquals(2, subtract)
            }
        }
    }
})

Notice that both in this style, as the one above, the entire test is described inside of the Spek constructor. Each specification needs to inherit from the class Spek defined in the org.jetbrains.api package.

Assertion Framework

Spek doesn’t have any built-in assertions, luckily there are several libraries in the wild that do. Below are several suggestions:

  • org.jetbrains.kotlin:kotlin-test (make sure to use the -ea VM option in your Spek run configuration when using this one or Kotlin’s built in assertions)

  • HamKrest

  • Expekt

  • Kluent

Scopes

Spek provides 3 types of scopes, which are used to define your spec.

Test

Equivalent to test methods in JUnit, normally all assertions are done in this scope. Use it to define a test scope.

Group

May contain an arbitrary number of nested scopes for better grouping of your tests.

Action

Represents an action that will be performed and may contain any number of test scopes. You can think of each test as an assertion to validate the expected behaviour of the action performed.

Basic Structure

Your specifications need to inherit from the org.jetbrains.spek.api.Spek base class.

SimpleSpec.kt
object SimpleSpec: Spek({
    describe("a calculator") {
        val calculator = SampleCalculator()

        on("addition") {
            val sum = calculator.sum(2, 4)

            it("should return the result of adding the first number to the second number") {
                assertEquals(6, sum)
            }
        }

        on("subtraction") {
            val subtract = calculator.subtract(4, 2)

            it("should return the result of subtracting the second number from the first number") {
                assertEquals(2, subtract)
            }
        }
    }
})

Aside from describe, Spek also provides given and context to create group scopes.

Important
Due to how Spek is structured, group scopes are eagerly evaluated during the discovery phase. Any logic that needs to be evaluated before and/or after test scopes should be done using fixtures, which will be discussed in the next section.

Fixtures

Spek allows running arbitrary code before and after a group and test is executed.

Note
You can’t declare fixtures within action scopes.
FixtureSpec.kt
object FixtureSpec: Spek({
    describe("a group") {
        beforeGroup {
            ...
        }

        beforeEachTest {
            ...
        }

        context("a nested group") {

            beforeEachTest {
                ...
            }

            beforeEachTest {
                ...
            }

            it ("should work") { ... }
        }

        it("do something") { ... }

        afterEachTest {
            ...
        }

        afterGroup {
            ...
        }
    }
})

Ignoring tests

Each scope method have a variant prefixed with x (e.g. xdescribe, xit, etc…​), which will Spek ignore when executing the spec.

Subjects

Note
This feature is currently experimental.

Normally you will only have a single class as the SUT. Spek provides an idiomatic way of writing this types of tests and removing a lot of boilerplate code.

SimpleCalculatorSpec.kt
object SimpleCalculatorSpec: SubjectSpek<Calculator>({
    subject { Calculator() } (1)

    it("should return the result of adding the first number to the second number") {
        assertEquals(6, subject.sum(2, 4)) (2)
    }

    it("should return the result of subtracting the second number from the first number") {
        assertEquals(2, subject.subtract(4, 2)) (2)
    }
})
  1. Tell Spek how to instantiate the subject, in this case a Calculator. This will be invoked for every test scope, which means each test scope will have a unique instance.

  2. Use subject to access the instance of the subject.

Shared Subjects

This feature is useful when testing subclasses, removing the need of duplicating test code.

Given AdvancedCalculator which is a subclass of Calculator, a typical test will look like the following:

AdvancedCalculatorSpec.kt
object AdvancedCalculatorSpec: SubjectSpek<AdvancedCalculator>({
    subject { AdvancedCalculator() }

    itBehavesLike(SimpleCalculatorSpec)

    describe("pow") {
        it("should return the power of base raise to exponent") {
            assertEquals(subject.pow(2, 2), 4)
        }
    }
})

This will include all scopes declared in SimpleCalculatorSpec.

FAQ

Q: What is Kotlin?

Kotlin is an Apache 2 OSS Language targetted at the JVM and JavaScript and is developed by JetBrains. It is aimed at being a concise modern language for general use. It also rocks!

Q: Is Spek a BDD or a TDD framework?

Spek in Dutch means Bacon, so you could think of it as a Bacon Driven Development framework. Being serious for a moment though, we (at least the original author) believe that there is a false distinction in frameworks around what TDD or BDD is. Unit tests are ultimately about defining the specifications of your system. As such, Spek is merely a specification framework if it can be called anything. For more information read What BDD has taught me.

Q: Can I have more than one on per given?

Yes you can. How you group your specifications is up to you.

Q: Can I have more than one it per on?

Yes you can. In real world applications, an action can lead to several reactions.

Q: Isn’t it bad to have more than one assertion per test?

Traditionally, in unit testing it’s been recommended that you should limit each test to one assertion with the idea that you test a single unit, and at the same time, find it easy to see where a test has failed. In Spek you can still comply with this guidance. You can have multiple it but you limit each to one assertion.

Q: Can I contribute?

Q: Is Spek an official JetBrains project?

No. Spek was a project initiated by Hadi Hariri and has multiple contributors both internally from JetBrains as well as externally. While it is hosted on the JetBrains account under GitHub, it is not a project that is officially supported.

Q: Is Spek the recommended way to test Kotlin code

Spek is just one of many options available to write tests, however any testing framework can be used with Kotlin, such as JUnit. Depending on your preferences, style and SUT, then Spek may or may not be the option for you.

Q: What is the licensing?

Spek is licensed under BSD.

Changelog

Releases
1.1.0
  • Renamed Dsl to SpecBody.

  • Added support for extensions.

  • Reworked subjects as an extension.

  • Support specs defined as objects.

  • Added beforeGroup and afterGroup fixtures.

  • Changed LifecycleAware signature to allow local delegation.

1.0.0
  • Initial release of Spek.

Core Team

Hadi Hariri
Ranie Jade Ramiso
Artem Zinnatullin

Main Contributors

In alphabetical order

Hadi Hariri
Ranie Jade Ramiso
Laura Kogler
Eugene Petrenko
Ilya Ryzhenkov

Resources