Power-assert compiler plugin
warning
The Power-assert compiler plugin is Experimental. It may be changed at any time. Use it only for evaluation purposes. We would appreciate your feedback in YouTrack.
The Kotlin Power-assert compiler plugin improves the debugging experience by providing detailed failure messages with contextual information. It simplifies the process of writing tests by automatically generating intermediate values in failure messages. It helps you understand why a test failed without needing complex assertion libraries.
This is an example message provided by the plugin:
Incorrect length
assert(hello.length == world.substring(1, 4).length) { "Incorrect length" }
| | | | | |
| | | | | 3
| | | | orl
| | | world!
| | false
| 5
Hello
The Power-assert plugin key features:
Enhanced error messages: The plugin captures and displays the values of variables and sub-expressions within the assertion to clearly identify the cause of failure.
Simplified testing: Automatically generates informative failure messages, reducing the need for complex assertion libraries.
Support for multiple functions: By default, it transforms
assert()
function calls but can also transform other functions, such asrequire()
,check()
, andassertTrue()
.
To enable the Power-assert plugin, configure your build.gradle(.kts)
file as follows:
// build.gradle.kts
plugins {
kotlin("multiplatform") version "2.0.0"
kotlin("plugin.power-assert") version "2.0.0"
}
// build.gradle
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '2.0.0'
id 'org.jetbrains.kotlin.plugin.power-assert' version '2.0.0'
}
The Power-assert plugin provides several options to customize its behavior:
functions
: A list of fully-qualified function paths. The Power-assert plugin will transform the calls to these functions. If not specified, onlykotlin.assert()
calls will be transformed by default.includedSourceSets
: A list of Gradle source sets that the Power-assert plugin will transform. If not specified, all test source sets will be transformed by default.
To customize the behavior, add the powerAssert {}
block to you build script file:
// build.gradle.kts
powerAssert {
functions = listOf("kotlin.assert", "kotlin.test.assertTrue", "kotlin.test.assertEquals", "kotlin.test.assertNull")
includedSourceSets = listOf("commonMain", "jvmMain", "jsMain", "nativeMain")
}
// build.gradle
powerAssert {
functions = ["kotlin.assert", "kotlin.test.assertTrue", "kotlin.test.assertEquals", "kotlin.test.assertNull"]
includedSourceSets = ["commonMain", "jvmMain", "jsMain", "nativeMain"]
}
Since the plugin is Experimental, you will see warnings every time you build your app. To exclude these warnings, add this @OptIn
annotation before declaring the powerAssert {}
block:
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
@OptIn(ExperimentalKotlinGradlePluginApi::class)
powerAssert {
...
}
This section provides examples of using the Power-assert compiler plugin.
See the complete code of the build script file build.gradle.kts
for all these examples:
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
{...}
Consider the following test with the assert()
function:
import kotlin.test.Test
class SampleTest {
@Test
fun testFunction() {
val hello = "Hello"
val world = "world!"
assert(hello.length == world.substring(1, 4).length) { "Incorrect length" }
}
}
If you run the testFunction()
test with the Power-assert plugin enabled, you get the explicit failure message:
Incorrect length
assert(hello.length == world.substring(1, 4).length) { "Incorrect length" }
| | | | | |
| | | | | 3
| | | | orl
| | | world!
| | false
| 5
Hello
To get a more complete error message, always inline the variable into the test function parameters. Consider the following test function:
class ComplexExampleTest {
data class Person(val name: String, val age: Int)
@Test
fun testComplexAssertion() {
val person = Person("Alice", 10)
val isValidName = person.name.startsWith("A") && person.name.length > 3
val isValidAge = person.age in 21..28
assert(isValidName && isValidAge)
}
}
The output of the executed code doesn't provide enough information to find the cause of the problem:
Assertion failed
assert(isValidName && isValidAge)
| |
| false
true
Inline the variable into the assert()
function:
class ComplexExampleTest {
data class Person(val name: String, val age: Int)
@Test
fun testComplexAssertion() {
val person = Person("Alice", 10)
assert(person.name.startsWith("A") && person.name.length > 3 && person.age > 20 && person.age < 29)
}
}
After execution, you get more explicit information about what went wrong:
Assertion failed
assert(person.name.startsWith("A") && person.name.length > 3 && person.age > 20 && person.age < 29)
| | | | | | | | | |
| | | | | | | | | false
| | | | | | | | 10
| | | | | | | Person(name=Alice, age=10)
| | | | | | true
| | | | | 5
| | | | Alice
| | | Person(name=Alice, age=10)
| | true
| Alice
Person(name=Alice, age=10)
The Power-assert plugin can transform various functions beyond assert
which is transformed by default. Functions like require()
, check()
, assertTrue()
, assertEqual()
and others can also be transformed, if they have a form that allows taking a String
or () -> String
value as the last parameter.
Before using a new function in a test, specify the function in the powerAssert {}
block of your build script file. For example, the require()
function:
// build.gradle.kts
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
@OptIn(ExperimentalKotlinGradlePluginApi::class)
powerAssert {
functions = listOf("kotlin.assert", "kotlin.require")
}
After adding the function, you can use it in your tests:
class RequireExampleTest {
@Test
fun testRequireFunction() {
val value = ""
require(value.isNotEmpty()) { "Value should not be empty" }
}
}
The output for this example uses the Power-assert plugin to provide detailed information about the failed test:
Value should not be empty
require(value.isNotEmpty()) { "Value should not be empty" }
| |
| false
The message shows the intermediate values that lead to the failure, making it easier to debug.
The Power-assert plugin supports soft assertions, which do not immediately fail the test but instead collect assertion failures and report them at the end of the test run. This can be useful when you want to see all assertion failures in a single run without stopping at the first failure.
To enable soft assertions, implement the way you will collect error messages:
fun <R> assertSoftly(block: AssertScope.() -> R): R {
val scope = AssertScopeImpl()
val result = scope.block()
if (scope.errors.isNotEmpty()) {
throw AssertionError(scope.errors.joinToString("\n"))
}
return result
}
interface AssertScope {
fun assert(assertion: Boolean, message: (() -> String)? = null)
}
class AssertScopeImpl : AssertScope {
val errors = mutableListOf<String>()
override fun assert(assertion: Boolean, message: (() -> String)?) {
if (!assertion) {
errors.add(message?.invoke() ?: "Assertion failed")
}
}
}
Add these functions to the powerAssert {}
block to make them available for the Power-assert plugin:
@OptIn(ExperimentalKotlinGradlePluginApi::class)
powerAssert {
functions = listOf("kotlin.assert", "kotlin.test.assert", "org.example.AssertScope.assert")
}
tip
You should specify the full name of the package where you declare the
AssertScope.assert()
function.
After that, you could use it in your test code:
// Import the assertSoftly() function
import org.example.assertSoftly
class SoftAssertExampleTest1 {
data class Employee(val name: String, val age: Int, val salary: Int)
@Test
fun `test employees data`() {
val employees = listOf(
Employee("Alice", 30, 60000),
Employee("Bob", 45, 80000),
Employee("Charlie", 55, 40000),
Employee("Dave", 150, 70000)
)
assertSoftly {
for (employee in employees) {
assert(employee.age < 100) { "${employee.name} has an invalid age: ${employee.age}" }
assert(employee.salary > 50000) { "${employee.name} has an invalid salary: ${employee.salary}" }
}
}
}
}
In the output, all the assert()
function error messages will be printed one after another:
Charlie has an invalid salary: 40000
assert(employee.salary > 50000) { "${employee.name} has an invalid salary: ${employee.salary}" }
| | |
| | false
| 40000
Employee(name=Charlie, age=55, salary=40000)
Dave has an invalid age: 150
assert(employee.age < 100) { "${employee.name} has an invalid age: ${employee.age}" }
| | |
| | false
| 150
Employee(name=Dave, age=150, salary=70000)
Look through a simple project with the plugin enabled and a more complex project with multiple source sets.