Is there an analogue of assertAll for the check method in Kotlin?

126 views Asked by At

junit has assertAll which allows you to perform all the required checks without throwing an exception for one of them, thereby ignoring the rest. For example:

assertAll(
    { assertEquals(a, b) },
    { assertEquals(c, d) },
)

Is there an analogue for the Kotlin language to perform several checks?
Something like checkAll, for example:

checkAll(
    check(a == b) { "error 1" },
    check(c == d) { "error 2" },
}

If both conditions are false, at the output we get:
error 1
error 2

2

There are 2 answers

0
possum On

You can construct something pretty simple that does that

fun check(checkExpression: Boolean, message: () -> String): Pair<Boolean, () -> String> {
    return Pair(checkExpression, message)
}


fun checkAll(vararg checks: Pair<Boolean, () -> String>): List<String> {
    return checks.filter { !it.first }.map { it.second() }
}

so

fun main() {
    val a = 1
    val b = 2
    val c = 3
    val d = 4

    val result = checkAll(
        check( a == b ) { "a != b" },
        check( c == d ) { "c != d" },
    )

    // result is list of strings ["a != b", "c != d"]
    println(result)
}

will give a list with the strings that don't pass

0
Klitos Kyriacou On

There is currently nothing like this in the Kotlin stdlib, but you can write your own.

A more idiomatic syntax in Kotlin might be to pass a block of checks, like this:

checkAll {
    check(a == b) { "error 1" }
    check(c == d) { "error 2" }
}

One advantage is that you could, for example, insert some additional code between checks.

You can't use the stdlib version of check because it immediately throws an IllegalStateException. But you can write checkAll so that it provides you with a custom version of check inside the block:

class CheckAllScope(val messages: MutableList<String>) {
    inline fun check(value: Boolean, lazyMessage: () -> Any) {
        if (!value)
            messages += lazyMessage().toString()
    }
}

fun checkAll(block: CheckAllScope.() -> Unit) {
    val messages = mutableListOf<String>()
    CheckAllScope(messages).block()
    if (messages.isNotEmpty())
        throw IllegalStateException("The following checks have failed: $messages")
}

Remember to add overloads to CheckAllScope for all the other versions of check, such as checkNotNull. See the stdlib source for a template of how to do this.

Also, you could do something similar for require, by writing a new function requireAll.

Drawback

If you do such soft assertions, you will lose the advantage of smart casts. For example, using the standard check function:

check(v != null)
check(v.size > 1)  // No need for v?.size, v is smart-cast to non-null