Using Spek with Maven

Running Spek tests with the Maven Build System

  • Java
  • Kluent
  • Kotlin
  • Maven
  • Spek
  • Testing
  • Walk-Through
Posted on

Using Spek with Maven

Running Spek tests with the Maven Build System

Rob Westwood

In this article we do a brief walk-through on how to use Maven to compile and run tests written using the Spek framework. Although we will be using the Kotlin language throughout similar steps are will be appropriate if you wish to use Spek specifications with Java code.

We first give a brief description of the Spek framework before giving a description as to how to convert JUnit tests to Spek and how to configure Maven to discover tests

What is Spek?

Spek is a specification framework for the JVM written by JetBrains. It is described as a specification framework rather than a testing framework as it encourages you to think in terms of describing what the system-under-test is meant to do rather than how to test it. This style of tests is often thought of as BDD-style.

As an example, consider the following simple test:

@Test
fun sayHiWithParameter() {
    assertEquals("Hello Kotlin!", sayHi("Kotlin"))
}

Even with this simplest of code fragments we see how the testing style has leant towards describing implementation of the function, rather than the what it is meant to do and why.

describe("A greeter") {
    given("the name of a person") {
        it("should greet that person by name") {
            assertEquals("Hello Kotlin", sayHi("Kotlin"))
        }
    }
}

This test improves the situation; we can see the context (we have "A greeter") which has the ability to greet people by name. This could be implemented in a standard test framework by careful naming of the test functions (fun aGreeter_givenTheNameOfAPerson_thenItShouldGreetThatPersonByName()), but that is a lot of words to parse and requires more discipline.

Spek has no built-in support for creating assertions in the specification. The above examples all use kotlin-test. However, I think that the style of assertions in Kluent more naturally fits the specification style of Spek, so I use that. In this case assertEquals("Hello Kotlin", sayHi("Kotlin")) will become sayHi("Kotlin") shouldEqual "Hello Kotlin".

Other options include HamKrest and Expekt.

Prerequisites

For this walk-through I assume that you have a working installation of Java 8 (I used OpenJDK 8) and Maven (I used 3.3.9). The instructions were tested under Ubuntu 16.04.

Creating a Kotlin Project

To ease creating some example code we use the kotlin-quickstart-archetype and use Maven to create our project.

 mvn archetype:generate \
    -DarchetypeGroupId=com.github.mhshams \
    -DarchetypeArtifactId=kotlin-quickstart-archetype \
    -DarchetypeVersion=0.1.0 \
    -DkotlinVersion=1.0.4 \
    -DgroupId=uk.co.catalystcomputing \
    -DartifactId=spek-maven-example \
    -Dversion=0.1-SNAPSHOT

cd spek-maven-example

Converting Tests to Spek

The quickstart archetype created us a JUnit Test file called GreetingTest.kt. I prefer to name my Spek specification files with a Spek suffix, so we'll rename it.

mv src/test/kotlin/uk/co/catalystcomputing/GreetingTest.kt \
    src/test/kotlin/uk/co/catalystcomputing/GreetingSpek.kt

We will now move the tests over to use Spek and Kluent by editing the GreetingSpek.kt file. The existing tests use JUnit and kotlin-test, so the imports need changing.

import org.junit.Test
import kotlin.test.assertEquals

becomes

import org.jetbrains.spek.api.Spek
import org.jetbrains.spek.api.dsl.describe
import org.jetbrains.spek.api.dsl.given
import org.jetbrains.spek.api.dsl.it
import org.amshove.kluent.*

Rather than using @Test or similar annotations, Spek specifications need to be a class which inherits from Spek; the specifications are passed in as a code block to the constructor. The test class changes from

class GreetingTest {
}

to

class GreetingSpek : Spek({
})

The important thing about a specification is we explicitly define a hierarchy of contexts (this is the basis of BDDs given/when/then). We are specifying a greeter which may be given the name of a person, or not. This forms the basis of the way we describe it.

class GreetingSpek : Spek({
    describe("A greeter") {
        given("the name of a person") {
            it("should greet that person by name") {

            }
        }
        given("no name of anyone") {
            it("should give a general greeting to the world") {

            }
        }
    }
})

We now put in the code which enables our specification to be checked.

class GreetingSpek : Spek({
    describe("A greeter") {
        given("the name of a person") {
            it("should greet that person by name") {
                sayHi("Kotlin") shouldEqual "Hello Kotlin!"
            }
        }
        given("no name of anyone to greet") {
            it("should give a general greeting to the world") {
                sayHi() shouldEqual "Hello World!"
            }
        }
    }
})

Configuring Maven to Use Spek

Finally, we need to change the pom.xml file so that Maven finds the dependencies and can run the tests. The changes are not difficult, but the accounts that I found all seem to miss a step.

Set up Repositories

Kluent is not available in the Maven Central repository, so we need to specify a repository for it.

<repositories>
    <repository>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <id>bintray-markusamshove-maven</id>
        <name>bintray</name>
        <url>http://dl.bintray.com/markusamshove/maven</url>
    </repository>
</repositories>

Declare Dependencies

According to the Spec documentation it is only necessary to include a dependency on spek-api. However following these instructions leaves Spek without anyway of executing the tests - it needs the plugin for the JUnit Platform engine from JUnit 5. The default project from the archetype explicitly pulls in JUnit 4, which needs to be deleted. Then we can the dependencies for Spek: the api (spek-api) and the JUnit Platform Engine integration (spek-junit-platform-engine). As we're using Kluent for the assertions we'll also add the dependency for that. The dependencies section becomes as follows:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <kotlin.version>1.0.4</kotlin.version>
    <spek.version>1.0.89</spek.version>
    <junit.platform.version>1.0.0-M2</junit.platform.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-stdlib</artifactId>
        <version>${kotlin.version}</version>
    </dependency>

    <dependency>
      <groupId>org.jetbrains.spek</groupId>
      <artifactId>spek-api</artifactId>
      <version>${spek.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.jetbrains.spek</groupId>
        <artifactId>spek-junit-platform-engine</artifactId>
        <version>${spek.version}</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.amshove.kluent</groupId>
        <artifactId>kluent</artifactId>
        <version>1.8</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Enable Test Discovery

Finally we need to configure Maven to find our tests. This sounds simple but took me a long while to figure this out. There are two problems:

  • The Maven Surefire plugin (used to run the tests) looks for Java filenames that begin or end with "Test" or end with "TestCase". The Kotlin compiler names its output files based on the name of the class, possibly with an additional suffix due to the additional classes it generates due to its semantics. As we named our test class GreetingSpek its output file is GreetingSpek.class, by default Surefire needs explicit configuration to look for these files by use of the <includes/> element.

  • Spek uses the JUnit Platform Engine but this is not supported directly from Surefire. Fortunately an optional provider is available. If we add it as a dependency to the maven-surefire-plugin <plugin/> element in the configuration it will be automatically picked up and used.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.19.1</version>
    <configuration>
        <includes>
            <include>**/*Spek*</include>
        </includes>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-surefire-provider</artifactId>
            <version>${junit.platform.version}</version>
        </dependency>
      </dependencies>
</plugin>

Conclusion

Spek gives a syntax for writing specifications that encourages a better way of writing tests. Converting existing tests to use Spek is not difficult, nor is integrating it with the Maven build system.

Links

  • The end result of following this walk-through is available on Github.