Simple test DSLs in Kotlin

Anders Sveen
6 min readDec 18, 2018

--

TL;DR: Kotlin is an awesome language. When we had to write a lot of unit tests for an important area of our code; Kotlin was fantastic. We could create a simple DSL for expressing the tests clearly and easily in only 40 lines of code.

When I code I find that the instant feedback loop of TDD is highly addictive. I feel lost and confused when I have to wait (to bother to do the next manual test) to find out if the code actually works. And I loathe doing manual testing. It is boring and error prone. So I write tests. A lot of tests.

That is not to say manual testing is not needed. I just work really hard to find good ways to create tests that give feedback and have low maintenance overhead. I save manual testing for the things that can not be tested automatically.

I wanted a “simple” low overhead way of setting up, expressing and testing many combinations of inputs and outputs.

We have some pieces of code that is complex and essential to our business. We think it is important to have a really good coverage of it. But there are many cases to test, with lots of inputs and outputs, so I got tired of copying and pasting code between tests.

Example test after building a simpel DSL in Kotlin

I wanted to find a concise way of writing tests that made creating new cases a breeze. What I ended up with (after a couple of iterations) is something similar to the code on the left.

If this looks a bit strange to you it is because it is Kotlin and its features that make it possible. Read on for a description of what is going on. :)

Alternatives; it has already been done

Over the years I have tried many of the frameworks that give you the ability to write test cases in a more a more “natural language”. A lot of them fall under the BDD umbrella. Fitnesse, RSpec and Cucumber come to mind.

I find “given, then, that” a nice way to think about some tests. Even if I might not adhere to it all the time.

I have yet to find one I really like. I keep falling back to JUnit and Hamkrest (Kotlin version of Hamcrest). Maybe I am just old school, but I find that I am efficient with it. And it is not (too) hard for others to understand.

Avoid the flames — Photo by Hush Naidoo on Unsplash

I think the complexity of the other solutions are not worth it most of the time. Remember: I like to write my own dependency injection. ;)

When you have many cases and inputs to a test, JUnit falls short. I use the @CsvSource from JUnit some times, and it is a good start. But I find that it only works in the simpler cases where the data is “flat” and can be represented easily in CSV. For out cases (even though it does not show in the example above) we need the input and verifications to be hierarchies and lists of things. The kind of things better expressed in JSON or YAML. Or just Kotlin… :)

The test and the rig that makes it work

This is the example from above and a simple test:

The naming of everything here is 100% custom and geared towards our domain and what we need to test. It kind of follows given/when/then but does not use the keywords.

The first line in the test above is actually just a function with a Unit as its last parameter:

This is very typical for Kotlin, it’s context-oriented programming or block- oriented as I like to call it.

The code above returns an object of type TestSetup. The next step “whenAvailabilityInformation” is a function on TestSetup class as shown below:

This method again returns a TestRequestInfo object. We will get to that shortly, but lets summarize so far: Each “block” in the test above is initiated by a function call (testConfig(), whenAvailabilityInformation()). What you do inside each block (setting variables like time and fromAddress) are defined by what vars the class of the returned object defines. So the TestRequestInfo class looks like this:

Recognize the fromAdress and toAddress? Also note that this defines the last “block” in the test example above. The one with the verifications. It does not return or apply an object like the previous functions, but actually takes the information already gathered (through the variables) and asserts what needs to be asserted in our case. The generateDeliveryTimesForRequest(…) is the business function that are under test.

In our real code there is a bit more ceremony in the set up of things, but most of it is encapsulated in the test-rig classes. So the tests describe “nicely” with strings for times and addresses, while the test-rig will convert it to ZonedTimeDate, Clock and Address objects that is used in the business code.

Kotlin features that enables this

Some of this is possible in other languages, and doing builders in Java can bring you close. But some Kotlin specific features helps us and makes this more “fluent”. And it is type safe! The major features should be:

Generics can give me headaches some times, and I find some of this almost as confusing. But I really think they are powerful tools, even outside tests. So learning them is well worth the struggle. :)

Not your golden hammer

I do not do this for all my tests or all areas. Just where I find that it is needed. It is another tool in the toolbox that I might reach for when:

  • I need to test many combinations of inputs and outputs
  • The domain logic is complex
  • The domain logic is essential to the business

Warning: It can be confusing, and I can have a hard time extending it. This is a highly specific micro framework. Frameworks adds overhead and complexity.

Use this tool where it suits you. We find that the added complexity is worth it because:

  • Whenever the domain interfaces change we fix it in only a few places and all our tests run again (most of the changes are in the test rig, not the individual tests).
  • If we break something it is easy to see which cases are broken because the tests are concise.
  • When someone comes to us with a possible bug it is easy and fast to write a case to test it. Some times it fails, some times it does not. At least we know, and can look at other sources of confusion or real bugs. :)

A “Working” example

A complete file of the example can be found here: https://github.com/PorterAS/dependency-injection/blob/master/src/test/kotlin/WritingTestDslTest.kt

Happy coding. :)

Credits

I Googled a lot to figure out these things, so I am really not sure which ones I used. Here are some of the articles I am pretty sure I used:

Photo by Fatos Bytyqi on Unsplash

--

--

Anders Sveen
Anders Sveen

Written by Anders Sveen

Passionate agile developer and mentor for hire @ Mikill Digital

No responses yet