DEV Community

Stephen Samuel for Kotest

Posted on

Kotest Release 4.3

The Kotest team is pleased to announce the release of Kotest 4.3.0.

This blog covers some of the new features added in this release.
For the full list, see the changelog.

New and improved data driven testing

Kotest has improved its data driven testing support, directly integrating into the framework. This means it will now automatically generate individual test case entries.

As an example, lets test a function that returns true if the input values are valid pythagorean triples.

fun isPythagTriple(a: Int, b: Int, c: Int): Boolean = a * a + b * b == c * c
Enter fullscreen mode Exit fullscreen mode

We start by writing a data class that will hold each row - a set of inputs.

data class PythagTriple(val a: Int, val b: Int, val c: Int)
Enter fullscreen mode Exit fullscreen mode

Next we invoke the function forAll inside a test case, passing in one or more of these data classes, and a
lambda that performs some test logic for a given row.

context("Pythag triples tests") {
    forAll(
       PythagTriple(3, 4, 5),
       PythagTriple(6, 8, 10),
       PythagTriple(8, 15, 17),
       PythagTriple(7, 24, 25)
    ) { (a, b, c) ->
        isPythagTriple(a, b, c) shouldBe true
    }
}
Enter fullscreen mode Exit fullscreen mode

Kotest will automatically generate a test case for each input row, as if you had manually written a seperate test case for each.

data test example output

For full documentation click here.

EnabledIf annotation on specs

It can be useful to avoid instantiating a spec entirely, and often we can do that via test tags. But if you want to do this with some bespoke code, then the annotation EnabledIf has been added.

Annotate a spec with EnabledIf, passing in a class that extends from EnabledCondition and that condition will be invoked at runtime to determine if the spec should be instantiated. The EnabledCondition implementation must have a zero arg constructor.

For example, lets make a condition that only executes a test if it is midnight.

class EnabledIfMidnight : EnabledCondition {
   override fun enabled(specKlass: KClass<out Spec>): Boolean = LocalTime.now().hour == 0
}
Enter fullscreen mode Exit fullscreen mode

And then attach that to a spec:

@EnabledIf(EnabledIfMidnight::class)
class EnabledIfTest : FunSpec() {
   init {
      test("tis midnight when the witches roam free") {
        // test here
      }
   }
}
Enter fullscreen mode Exit fullscreen mode

TestCase severity

Test case can be conditionally executed via test tags, and now also by severity levels. The levels are BLOCKER, CRITICAL, NORMAL, MINOR, and TRIVIAL.

We can mark each test case with a severity level:

class MyTest : FunSpec() {
   init {
      test("very very important").config(severity = TestCaseSeverityLevel.CRITICAL) {
        // test here
      }
   }
}
Enter fullscreen mode Exit fullscreen mode

Say we only want to execute tests that are CRITICAL or higher, we can execute with the system property kotest.framework.test.severity=CRITICAL

This can be useful if we have a huge test suite and want to run some tests first in a seperate test run.

By default, all tests are executed.

Disabling source references

Whenever a test case is created, Kotest creates a stack trace so that it can link back to the test case. The stack trace contains the filename and line number which the Intellij Plugin uses to create links in the test window. It calls these the sourceref.

If you have 1000s of tests and are encountering some slowdown when executing the full suite via gradle, you can now disable
the generation of these sourcerefs by setting the system property kotest.framework.sourceref.disable=true

Generally speaking, this is only of use if you have a huge test suite and mostly aimed at CI builds.

Make engine dependency free

A test framework is one of the lowest levels of dependences in an ecosystem. As Kotest is used by many Kotlin libraries, a clash can occur if Kotest and your project are using the same dependencies but with different versions.

It is beneficial then if Kotest has as few dependencies as possible. To this aim, 4.3.0 has seen the dependencies for the Kotest framework reduced to just Classgraph (to scan for specs), Mordant (for console output), and opentest4j.

Matchers return 'this' for easy chaining

In the opinion of this author, Kotest has the most comprehensive assertion support for Kotlin. Now they just became more convienient, by allowing you to chain assertions together if you wish.

So, instead of

val employees: List<Employee> = ...
employees.shouldBeSorted()
employees.shouldHaveSize(4)
employees.shouldContain(Employee("Sam", "Chicago"))
Enter fullscreen mode Exit fullscreen mode

You can now do

val employees: List<Employee> = ...
employees.shouldBeSorted()
         .shouldHaveSize(4)
         .shouldContain(Employee("Sam", "Chicago"))
Enter fullscreen mode Exit fullscreen mode

Of course, this is entirely optional.

Property test module for kotlinx datetime

Kotest's expansive property test library now includes generators for the incubating kotlinx datetime library.

Add the module kotest-property-datetime to your build. These generators are available for JVM and JS.

For example:

forAll(Arb.datetime(1987..1994)) { date ->
   isValidStarTrekTngSeason(date) shouldBe true
}
Enter fullscreen mode Exit fullscreen mode

Option to strip whitespace from test names

If you like to define test names over multiple lines, Kotest will now strip out leading, trailing and repeated whitespace from test names.

For example, the following spec:

class MySpec : StringSpec() {
  init {
   """this is a
      test spanning multiple lines""" { }
  }
}
Enter fullscreen mode Exit fullscreen mode

Would normally be output as

this is a      test spanning multiple lines
Enter fullscreen mode Exit fullscreen mode

By setting the configuration object removeTestNameWhitespace to true, this would instead by output as

this is a test spanning multiple lines
Enter fullscreen mode Exit fullscreen mode

Thanks

Huge thanks to all who contributed to this release:

AJ Alt, Alex Facciorusso, Ashish Kumar Joy, J Phani Mahesh, Jasper de Vries, Javier Segovia Córdoba,
Josh Graham, KeremAslan, Leonardo Colman, Michał Sikora, Mitchell Yuwono, Neenad Ingole, Rick Busarow,
SergKhram, Sergei Khramkov, crazyk2, sksamuel

Top comments (0)