Codementor Events

Parametrized tests with Spek

Published Apr 25, 2018
Parametrized tests with Spek

Originally published on: blog.kotlin-academy.com by Jarosław Michalik

Everyone knows the situations when we have to check our system for various input. A good example would be a login/password validation performed both on front and backend. There are many approaches to create a test code which covers all necessary cases, we can just copy-paste methods (not recommended), make many assertions in one test method (we can lose some valuable error info) or use parametrized test functionality bundled within test framework we use. Keep in mind that there are more options available.

If you want to have several test cases in jUnit4, you have to use parametrized runner and add some methods with dedicated annotations, and implement method returning a list of parameters and expected output:

@RunWith(Parameterized::class)
class FibonacciTest(private val fInput: Int, private val fExpected: Int) {

    @Test
    fun test() {
        assertEquals(fExpected, Fibonacci.compute(fInput))
    }

    companion object {
        @JvmStatic
        @Parameterized.Parameters
        fun data(): Collection<Array<Any>> {
            return listOf(
                    arrayOf<Any>(0, 0),
                    arrayOf<Any>(1, 1),
                    arrayOf<Any>(2, 1), 
                    arrayOf<Any>(3, 2),
                    arrayOf<Any>(4, 3),
                    arrayOf<Any>(5, 5),
                    arrayOf<Any>(6, 8)))
        }
    }
}

Pretty ugly, huh? A full example in Java can be found here:
https://github.com/junit-team/junit4/wiki/Parameterized-tests

We can do better

Imagine, if we could write our tests like we write our code. Clear output, no annotation driven development, and no unnecessary static methods. That is were Spek comes to help.

If you are not familiar with using Spek in unit testing check my article https://medium.com/@rozkminiacz/testing-with-spek-mvp-d94fd924af86
or other blog posts.

Consider simple distance converter — we parse given distance in meters to desired unit.

It’s a simple case —the implementation is trivial. It only takes one switch or when and a function to round number with desired precision to solve this case.

Despite the fact, that the implementation is simple, we should prepare unit test it. Using Spek we can also write BDD style tests and prepare a specification for one approach.

class DistanceConverterSpecificiation : Spek({
    describe("distance converter") {
        val distanceConverter: DistanceConverter by memoized {
            DistanceConverter()
        }
        on("distance 61888.123m") {
            val testDistance = 61888.123
            it("should return >50km") {
                val convert = distanceConverter.convert(testDistance)
                assertTrue(convert.contentEquals(">50km"))
            }
        }
        on("distance 38777.23m") {
            val testDistance = 38777.23
            it("should return 38.8km") {
                val convert = distanceConverter.convert(testDistance)
                assertTrue(convert.contentEquals("38.8km"))
            }
        }
    }
})

Run this snippet and check results? We have tested two cases, however, we are still repeating ourselves. It is as long and ugly as @Parametrized jUnit test or just test with many methods.

Spek DSL is a huge lambda expression, so we can use any Kotlin language features.

Let’s start with defining our test cases and expected outcome using Map<Double, String>:

val testCases = mapOf(
    61888.123 to ">50 km",
    38777.23 to "38.8 km",
    16984.44 to "17.0 km",
    987.98 to "988 m"
)

Now we can use forEach{} to generate desired test cases:

testCases.forEach { value, expectedValue ->
            on("$value"){
                it("should print $expectedValue"){}
            }
        }

Finally, we can make some assertion. A full example will look like this:


class DistanceConverterSpecificiation : Spek({
    describe("distance converter") {
        val testCases = mapOf(
                61888.123 to ">50 km",
                38777.23 to "38.8 km",
                16984.44 to "17.0 km",
                987.98 to "988 m"
        )
        val converter = DistanceConverter()
        testCases.forEach { value, expectedValue ->
            on("$value"){
                it("should print $expectedValue"){
                    assertTrue(converter.convert(value).contentEquals(expectedValue))
                }
            }
        }
    }
})

Conclusion

When creating unit tests we have to be sure that all (reasonable) cases are covered. In provided example (distance converter) we used Map<Double, String> and forEach{k,v -> } to parametrize our test and create on() ActionBody and it() TestBody in a stream. We have performed checks on each value, asserted expected outcome and displayed test result nicely in our IDE. We haven’t lost any valuable info, and we can add another test cases in clear Kotlin syntax.

Using Spek gives you the full power of Kotlin — you can write your tests just like you write your code and make use of language features.

I hope that you find this article useful, and it will help you look at parametrized tests in a different way.


To be up-to-date with great news on Kotlin Academy, subscribe to the newsletter and observe Twitter.

Discover and read more posts from Kotlin Academy
get started