Kotlin test assertions with informative failure messages using Strikt

Share on:

When I started writing Kotlin code a couple of years ago, one of the main things I missed after previously writing tests with Groovy/Spock were the easy yet powerful assertions with Groovy's power assert - not to mention the expressive failure messages when a test failed. Groovy's power assert prints out the .toString() of every element in the assertion, making it easier to diagnose what went wrong from reading the test report.

While you can test Kotlin code with Spock and Groovy - and I initially did - soon you will start running into challenges that make testing Kotlin code with non-Kotlin tests painful. Native Kotlin features such as copying data classes while modifying a parameter or two can very helpful when working with test data inside a test case, but those aren't supported outside of Kotlin. And the big one - testing co-routines is easy in Kotlin with runBlocking { } but again you won't have access to that Kotlin feature in Groovy test code.

After repeatedly encountering these headaches when trying to test Kotlin code with Groovy, I finally gave in and starting writing tests in Kotlin as well. But I didn't want to lose all the advantages of assertions with Groovy's power assert, so I went searching for similar capabilities in the Kotlin world and found Strikt. Strikt is a fluent Kotlin assertion library created by Rob Fletcher that made it easy to write flexible and readable assertions while also giving very helpful failure messages when my tests (inevitably) failed.

Getting started

Getting started with Strikt is easy, just add this dependency to your project.

1repositories {
2    mavenCentral()
3}
4
5dependencies {
6    testImplementation "io.strikt:strikt-core:0.29.0"
7}

At the time of writing this post, the current version was 0.29.0 but you can find the latest version on the Strikt changelog.

And since Strikt is solely an assertion library, you can use it with any test framework (Kotest, JUnit, Spek, etc.)

Basic assertions

The most basic and common assertion is verifying that a value under test matches the value you expect. In Strikt, the syntax for this is expectThat(actual).isEqualTo(expected), for example:

1expectThat(response.status()).isEqualTo(HttpStatusCode.OK)

You can also verify multiple fields in an object in one fluid assertion using .get(). For example, this assertion verifies four fields of an object match their expected values:

1expectThat(coverage.overallStats.branchStat) {
2    get { covered }.isEqualTo(191)
3    get { missed }.isEqualTo(57)
4    get { total }.isEqualTo(248)
5    get { coveredPercentage }.isEqualTo(BigDecimal("77.02"))
6}

Assertion failure messages

As mentioned above, one of my favorite features of Strikt is how informative its error messages are. In this example, I'm verifying the size of a collection and that it contains specific elements. And another nice feature of Strikt is that its assertions are chainable, so I can write those verifications in a concise yet readable way.

1expectThat(lineNumbers).hasSize(4).contains(16, 18, 20, 22)

When that fails, Strikt prints out the contents of the lineNumbers collection and the results of each individual part of the assertion - separated out in a very readable way:

1org.opentest4j.AssertionFailedError: ▼ Expect that [16, 18, 20, 21]:
2  ✓ has size 4
3  ✗ contains the elements [16, 18, 20, 22]
4    ✓ contains 16
5    ✓ contains 18
6    ✓ contains 20
7    ✗ contains 22

Extensive assertion types

Strikt includes many, many different assertions. Below is only a small sampling of the assertions Strikt supports - the full list is available at https://strikt.io/kotlindoc/core/strikt/assertions/

Collection assertions

Working with collections is common, and Strikt includes many assertions that make verifying collections easy. We already saw an example of the .hasSize() and .contains() assertions, and Strikt also has the .any() assertion to verify that one element in a collection matches a set of conditions:

1expectThat(testSuiteList).hasSize(2).any {
2    get { name }.isEqualTo("cypress/integration/attachments.spec.js")
3    get { testCases }.isNotNull().hasSize(2)
4}

String assertions

Strikt also has specialized assertions for verifying strings, such as .startsWith():

1val mergedBlob = ResultsXmlMerger.cleanAndMergeBlob(resultsBlob)
2
3expectThat(mergedBlob).startsWith("""<?xml version="1.0" encoding="UTF-8"?>""")

Expected exceptions

1expectCatching { coverageService.saveReport(coverageFilePayload, publicId) }
2    .isFailure()
3    .isA<DataAccessException>()

The full list of assertion options related to expected exceptions is available on the Strikt website at https://strikt.io/wiki/expecting-exceptions/

Conclusion

As we've seen, Strikt is a powerful and flexible Kotlin assertion library that gives very helpful and information failure messages, plus it works with any test framework.

Big thanks to Rob Fletcher and the other Strikt contributors for creating this library!