Core assertion building blocks for Kotest testing framework providing foundational utilities like shouldBe for all platforms
—
The error handling system provides sophisticated error collection, context management, and reporting capabilities. It includes clue systems for enhanced debugging, configurable error collection modes, and comprehensive failure creation utilities.
Central interface for managing assertion errors during test execution.
/**
* Interface for collecting and managing assertion errors
*/
interface ErrorCollector {
/** Current nesting depth for error context */
var depth: Int
/** Current test subject for error context */
var subject: Printed?
/**
* Get the current error collection mode
* @return Current ErrorCollectionMode
*/
fun getCollectionMode(): ErrorCollectionMode
/**
* Set the error collection mode
* @param mode New ErrorCollectionMode to use
*/
fun setCollectionMode(mode: ErrorCollectionMode)
/**
* Get all collected errors
* @return List of collected Throwable instances
*/
fun errors(): List<Throwable>
/**
* Add an error to the collection
* @param t Throwable to add
*/
fun pushError(t: Throwable)
/**
* Clear all collected errors
*/
fun clear()
/**
* Add a contextual clue to the error context stack
* @param clue Clue to add for error context
*/
fun pushClue(clue: Clue)
/**
* Remove the most recent clue from the context stack
*/
fun popClue()
/**
* Get the current clue context stack
* @return List of active clues
*/
fun clueContext(): List<Clue>
}
/**
* Error collection behavior modes
*/
enum class ErrorCollectionMode {
/** Collect errors without immediately throwing */
Soft,
/** Throw errors immediately when they occur */
Hard
}Interface for tracking the number of assertions executed during testing.
/**
* Interface for counting assertions during test execution
*/
interface AssertionCounter {
/**
* Increment the assertion count
*/
fun inc()
/**
* Get the current assertion count
* @return Current count as Int
*/
fun get(): Int
/**
* Reset the assertion count to zero
*/
fun reset()
}Utility functions for creating and throwing assertion errors.
/**
* Create an AssertionError with the given message
* @param message Error message
* @return AssertionError instance
*/
fun failure(message: String): AssertionError
/**
* Create an AssertionError with message and cause
* @param message Error message
* @param cause Underlying cause throwable
* @return AssertionError instance with cause
*/
fun failure(message: String, cause: Throwable?): AssertionError
/**
* Create a formatted failure with expected/actual values
* @param expected Expected value wrapper
* @param actual Actual value wrapper
* @param prependMessage Optional message to prepend
* @return Formatted Throwable
*/
fun failure(expected: Expected, actual: Actual, prependMessage: String = ""): Throwable
/**
* Immediately throw an assertion failure with the given message
* @param msg Error message
* @return Nothing (function never returns)
*/
fun fail(msg: String): NothingFunctions for asserting that code blocks throw or don't throw expected exceptions.
/**
* Assert that the given block throws any exception
* @param block Code block that should throw
* @return The AssertionError that was thrown
*/
inline fun shouldFail(block: () -> Any?): AssertionError
/**
* Assert that the given block throws an exception with a specific message
* @param message Expected error message
* @param block Code block that should throw
* @return The AssertionError that was thrown
*/
inline fun shouldFailWithMessage(message: String, block: () -> Any?): AssertionError
/**
* Assert that the given block throws an exception of type T
* @param T The expected exception type
* @param block Code block that should throw T
* @return The exception of type T that was thrown
*/
inline fun <reified T : Throwable> shouldThrow(block: () -> Any?): T
/**
* Assert that the given block throws exactly type T (not subclasses)
* @param T The expected exception type
* @param block Code block that should throw exactly T
* @return The exception of type T that was thrown
*/
inline fun <reified T : Throwable> shouldThrowExactly(block: () -> Any?): T
/**
* Assert that the given block throws type T, returning Unit
* @param T The expected exception type
* @param block Code block that should throw T
*/
inline fun <reified T : Throwable> shouldThrowUnit(block: () -> Any?)
/**
* Assert that the given block throws exactly type T, returning Unit
* @param T The expected exception type
* @param block Code block that should throw exactly T
*/
inline fun <reified T : Throwable> shouldThrowExactlyUnit(block: () -> Any?)
/**
* Assert that the given block throws any exception
* @param block Code block that should throw
* @return The Throwable that was thrown
*/
inline fun shouldThrowAny(block: () -> Any?): Throwable
/**
* Assert that the given block throws T with a specific message
* @param T The expected exception type
* @param message Expected error message
* @param block Code block that should throw T with message
* @return The exception of type T that was thrown
*/
inline fun <reified T : Throwable> shouldThrowWithMessage(message: String, block: () -> Any?): T
/**
* Assert that the given block throws T with a specific message, returning Unit
* @param T The expected exception type
* @param message Expected error message
* @param block Code block that should throw T with message
*/
inline fun <reified T : Throwable> shouldThrowUnitWithMessage(message: String, block: () -> Any?)
/**
* Assert that the given block throws any exception with a specific message
* @param message Expected error message
* @param block Code block that should throw with message
* @return The Throwable that was thrown
*/
inline fun shouldThrowMessage(message: String, block: () -> Any?): Throwable
/**
* Assert that the given block does NOT throw type T
* @param T The exception type that should not be thrown
* @param block Code block that should not throw T
* @return The result of executing the block
*/
inline fun <reified T : Throwable, R> shouldNotThrow(block: () -> R): R
/**
* Assert that the given block does NOT throw exactly type T
* @param T The exception type that should not be thrown
* @param block Code block that should not throw exactly T
* @return The result of executing the block
*/
inline fun <reified T : Throwable, R> shouldNotThrowExactly(block: () -> R): R
/**
* Assert that the given block does NOT throw type T, returning Unit
* @param T The exception type that should not be thrown
* @param block Code block that should not throw T
*/
inline fun <reified T : Throwable> shouldNotThrowUnit(block: () -> Any?)
/**
* Assert that the given block does NOT throw exactly type T, returning Unit
* @param T The exception type that should not be thrown
* @param block Code block that should not throw exactly T
*/
inline fun <reified T : Throwable> shouldNotThrowExactlyUnit(block: () -> Any?)
/**
* Assert that the given block does NOT throw any exception
* @param block Code block that should not throw
* @return The result of executing the block
*/
inline fun <R> shouldNotThrowAny(block: () -> R): R
/**
* Assert that the given block does NOT throw with a specific message
* @param message Error message that should not occur
* @param block Code block that should not throw with this message
* @return The result of executing the block
*/
inline fun <R> shouldNotThrowMessage(message: String, block: () -> R): RFunctions for adding contextual information to improve error messages.
/**
* Execute code with additional contextual information for error reporting
* @param clue Contextual information to include in error messages
* @param thunk Code block to execute with the clue context
* @return Result of executing the thunk
*/
inline fun <R> withClue(clue: Any?, thunk: () -> R): R
/**
* Execute code with lazily-evaluated contextual information
* @param clue Function that provides contextual information when needed
* @param thunk Code block to execute with the clue context
* @return Result of executing the thunk
*/
inline fun <R> withClue(crossinline clue: () -> Any?, thunk: () -> R): R
/**
* Use this value as contextual information for the given block
* @param block Code block to execute with this value as context
* @return Result of executing the block
*/
inline fun <T : Any?, R> T.asClue(block: (T) -> R): RGlobal configuration object for assertion behavior.
/**
* Global configuration object for assertion behavior
*/
object AssertionsConfig {
/** Whether to show detailed diffs for data classes */
val showDataClassDiff: Boolean
/** Minimum string length before showing diff output */
val largeStringDiffMinSize: Int
/** Format string for multi-line diffs */
val multiLineDiff: String
/** Maximum number of errors to output before truncating */
val maxErrorsOutput: Int
/** Maximum size for map diff output */
val mapDiffLimit: Int
/** Maximum collection size for enumeration in error messages */
val maxCollectionEnumerateSize: Int
/** Whether to disable NaN equality checking */
val disableNaNEquality: Boolean
/** Maximum collection size for printing in error messages */
val maxCollectionPrintSize: ConfigValue<Int>
}
/**
* Interface for configuration values with metadata about their source
*/
interface ConfigValue<T> {
/** Description of where this configuration value was loaded from */
val sourceDescription: String?
/** The actual configuration value */
val value: T
}
/**
* Environment-based configuration value implementation
*/
class EnvironmentConfigValue<T>(
private val name: String,
private val defaultValue: T,
val converter: (String) -> T
) : ConfigValue<T>import io.kotest.assertions.*
import io.kotest.matchers.shouldBe
// Create and throw assertion errors
fun validateAge(age: Int) {
if (age < 0) {
throw failure("Age cannot be negative: $age")
}
if (age > 150) {
throw failure("Age seems unrealistic: $age")
}
}
// Immediate failure
fun processUser(user: User?) {
user ?: fail("User cannot be null")
// Process user...
}import io.kotest.assertions.*
import io.kotest.matchers.shouldBe
// Assert that code throws any exception
val error = shouldFail {
divide(10, 0) // Should throw ArithmeticException
}
// Assert specific exception type
val arithmeticError = shouldThrow<ArithmeticException> {
divide(10, 0)
}
arithmeticError.message shouldBe "Division by zero"
// Assert exact exception type (not subclasses)
shouldThrowExactly<IllegalArgumentException> {
validateInput("")
}
// Assert exception with specific message
shouldThrowWithMessage<IllegalArgumentException>("Input cannot be empty") {
validateInput("")
}
// Assert any exception with specific message
shouldThrowMessage("Invalid operation") {
performInvalidOperation()
}
// Assert that code does NOT throw specific exception
val result = shouldNotThrow<NullPointerException> {
safeOperation() // Should complete successfully
}
// Assert that code does NOT throw any exception
val data = shouldNotThrowAny {
loadData() // Should complete without throwing
}
// Unit-returning variants (when you don't need the exception/result)
shouldThrowUnit<IllegalStateException> {
invalidStateOperation()
}
shouldNotThrowUnit<IOException> {
fileOperation()
}import io.kotest.assertions.*
import io.kotest.matchers.shouldBe
data class User(val name: String, val age: Int, val email: String)
// Basic clue usage
val user = User("Alice", 25, "alice@example.com")
withClue("Validating user: $user") {
user.name shouldBe "Alice"
user.age shouldBe 25
user.email shouldBe "alice@example.com"
}
// If any assertion fails, the error message will include:
// "Validating user: User(name=Alice, age=25, email=alice@example.com)"import io.kotest.assertions.*
import io.kotest.matchers.shouldBe
// Expensive clue computation only happens on failure
withClue({ "Current system state: ${getExpensiveSystemState()}" }) {
performComplexOperation() shouldBe expectedResult
}
// getExpensiveSystemState() is only called if the assertion failsimport io.kotest.assertions.*
import io.kotest.matchers.shouldBe
data class DatabaseRecord(val id: Int, val data: String)
val record = DatabaseRecord(123, "important data")
// Use the record itself as context
record.asClue { rec ->
rec.id should beGreaterThan(0)
rec.data.isNotEmpty() shouldBe true
}
// On failure, includes: "DatabaseRecord(id=123, data=important data)"import io.kotest.assertions.*
import io.kotest.matchers.shouldBe
val users = listOf(
User("Alice", 25, "alice@example.com"),
User("Bob", 30, "bob@example.com")
)
withClue("Processing user list") {
users.forEachIndexed { index, user ->
withClue("User at index $index") {
user.asClue { u ->
u.name.isNotEmpty() shouldBe true
u.age should beGreaterThan(0)
u.email should contain("@")
}
}
}
}
// Nested clues provide hierarchical context in error messagesimport io.kotest.assertions.*
// Example of using error collection (implementation-dependent)
fun validateMultipleUsers(users: List<User>) {
// Assuming we have access to an ErrorCollector instance
val collector = getErrorCollector()
// Set to soft mode to collect all errors
collector.setCollectionMode(ErrorCollectionMode.Soft)
users.forEach { user ->
try {
validateUser(user)
} catch (e: AssertionError) {
collector.pushError(e)
}
}
// Check if any errors were collected
val errors = collector.errors()
if (errors.isNotEmpty()) {
throw failure("Multiple validation errors: ${errors.size} users failed validation")
}
}import io.kotest.assertions.*
import io.kotest.matchers.shouldBe
data class TestScenario(val name: String, val input: Any, val expected: Any)
fun runTestScenarios(scenarios: List<TestScenario>) {
scenarios.forEach { scenario ->
withClue("Test scenario: ${scenario.name}") {
scenario.input.asClue { input ->
withClue("Expected: ${scenario.expected}") {
val result = processInput(input)
result shouldBe scenario.expected
}
}
}
}
}
// Provides rich context like:
// "Test scenario: Valid email processing"
// "Input: test@example.com"
// "Expected: true"
// "but was: false"import io.kotest.assertions.*
import io.kotest.matchers.shouldBe
fun validateApiResponse(response: ApiResponse) {
response.asClue { resp ->
withClue("Response validation") {
resp.status shouldBe 200
resp.data.shouldNotBeNull()
withClue("Response timing validation") {
resp.responseTime should beLessThan(1000)
}
withClue("Response content validation") {
resp.data.asClue { data ->
data.size should beGreaterThan(0)
data.forAll { item ->
item.id should beGreaterThan(0)
}
}
}
}
}
}The error handling system integrates with the print system for formatting values in error messages:
import io.kotest.assertions.*
import io.kotest.assertions.print.*
// Custom printed values appear in error messages
data class CustomData(val value: String)
// Error messages will use the print system to format CustomData instances
val data = CustomData("test")
data shouldBe CustomData("other") // Uses print system for clear error outputError handling works seamlessly with the matcher system:
import io.kotest.assertions.*
import io.kotest.matchers.*
// Matcher failures automatically use the error handling system
"hello" should startWith("hi") // Uses failure() internally for error creationasClue to include object state in errorsfail() for immediate termination on invalid conditionsInstall with Tessl CLI
npx tessl i tessl/maven-io-kotest--kotest-assertions-shared-jvm