Twitter LinkedIn Github

JetBrains

Take a look at the following code, an example of a specification written using spek

given("a calculator", {

  val calculator = Calculator()

  on("calling sum with two numbers", {

    val sum = calculator.sum(2, 3)

    it("should return the sum of the two numbers", {

      shouldEqual(5, sum)
    }
  }
}

In this code, given, on and it are all functions.

Kotlin allows top-level functions to be defined (without the need for them to belong to a class). As such, we could merely declare these functions in a single file as below:

public fun given(description : String, givenExpression: () -> Unit) {

    // code omitted
}

public fun on(description: String, onExpression: () -> Unit) {

    // code omitted
}

public fun it(description: String, itExpression: () -> Unit)  {

   // code omitted
}

At first sight it might not seem like a bad approach, but it is. The problem is that you cannot prevent someone from calling the code like so:

 given("I have nothing", {
        it("should do nothing", {
            on("not doing anything", {

            })
        })
    })

or even:

it("should do nothing", {
        given("I have nothing", {
            on("not doing anything", {

            })
        })
})

or any other weird combination for that matter.

This means that we would now have to put in additional runtime checks to verify that things are called in the correct manner; context has been set up before the action, the action has been executed before the assertions and so on and so forth. Pain.

Extension Function Literals

We’ve already seen that Kotin has extension functions, similar to C#. We’ve also seen that we can define extension functions as function literals:

var spaceReplace = { String.(char: Char): String -> this.replace(' ', char) }

val output = "This is an example".spaceReplace('_')

// outputs: This_is_an_example

Combining this with high-order functions, that is a function that takes a function as a parameter, we can do:

public fun given(description : String, givenExpression: Given.() -> Unit) {
  // code omitted
}

public class Given {
    public fun on(description: String, onExpression: On.() -> Unit) {
        // code omitted
    }
}

public class On {
    public fun it(description: String, itExpression: It.()->Unit) {
      // code omitted
    }
}

public class It {

    fun shouldEqual<T>(expected: T, actual: T) {
        assertEquals(expected, actual)
    }

    // rest of code omitted...
}

given is still a top-level function. However, the diference is that on and it are no longer top-level. Instead, we’re creating three different classes: Given, On and It.

Let’s take a look at the Given class. This class has a method that takes two parameters: a string, which is the description and a second parameter which is the extension function literal we’ve been talking about:

public class Given {
    public fun on(description: String, onExpression: On.() -> Unit) {
        // code omitted
    }
}

The parameter is an extension function on the On class, which takes no parameters and returns no value (Unit in Kotlin - void).

We repeat the same pattern with the On and It classes. By doing this, we now enforce a certain flow at compile time and prohibit out of order usage.

You can see also in the code, that the It class contains a series of assertion methods, exactly where they belong.

Summary

We can see that with some simple extension function literals, we can easily impose a series of restrictions on our DSL’s. Of course, in this case, the example is quite straightforward but the same thing can be applied to designing other DSL’s such as HTML builders et al.

[Thanks to @orangy for highlighting the issue and refactoring of Spek to this]