CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-jetbrains-kotlin--kotlin-test

Multiplatform testing framework providing unified API for writing tests across all Kotlin platforms with assertions and test annotations.

Pending
Overview
Eval results
Files

exception-testing.mddocs/

Exception and Failure Testing

Assertions for testing exception handling, failure scenarios, and expected error conditions. Essential for robust testing of error paths and edge cases.

Capabilities

Failure Assertions

fail Function

Marks a test as failed immediately with an optional message and cause.

/**
 * Marks a test as having failed if this point in the execution path is reached
 * @param message - Optional failure message
 * @return Nothing (function never returns)
 */
fun fail(message: String? = null): Nothing

/**
 * Marks a test as having failed with an optional message and cause exception
 * @param message - Optional failure message
 * @param cause - Optional exception that caused the failure
 * @return Nothing (function never returns)
 */
fun fail(message: String? = null, cause: Throwable? = null): Nothing

Usage Examples:

import kotlin.test.*

@Test
fun testConditionalFailure() {
    val result = performCriticalOperation()
    
    when (result.status) {
        Status.SUCCESS -> {
            // Test passes, continue validation
            assertTrue(result.data.isNotEmpty())
        }
        Status.RECOVERABLE_ERROR -> {
            // Expected failure scenario
            assertTrue(result.canRetry)
        }
        Status.FATAL_ERROR -> {
            fail("Critical operation failed: ${result.errorMessage}")
        }
        else -> {
            fail("Unexpected status: ${result.status}")
        }
    }
}

@Test
fun testWithCause() {
    try {
        val connection = establishConnection()
        if (connection == null) {
            fail("Failed to establish connection")
        }
    } catch (e: NetworkException) {
        fail("Network error during connection", e)
    }
}

Exception Assertions

assertFails Function

Asserts that a block of code throws any exception.

/**
 * Asserts that given function block fails by throwing an exception
 * @param block - Code block that should throw an exception
 * @return The exception that was thrown
 */
inline fun assertFails(block: () -> Unit): Throwable

/**
 * Asserts that given function block fails by throwing an exception
 * @param message - Optional message used as prefix for failure message
 * @param block - Code block that should throw an exception
 * @return The exception that was thrown
 */
inline fun assertFails(message: String?, block: () -> Unit): Throwable

Usage Examples:

@Test
fun testExceptionHandling() {
    // Test that division by zero throws
    val exception = assertFails {
        val result = 10 / 0
    }
    assertTrue(exception is ArithmeticException)
    
    // Test with custom message
    val parseException = assertFails("Should fail to parse invalid number") {
        "not-a-number".toInt()
    }
    assertTrue(parseException is NumberFormatException)
    
    // Inspect exception details
    val validationException = assertFails {
        validateEmail("invalid-email")
    }
    assertContains(validationException.message ?: "", "email")
}

@Test
fun testApiErrorHandling() {
    val client = ApiClient()
    
    // Test unauthorized access
    val authException = assertFails("Should fail with invalid token") {
        client.makeRequest("/protected", token = "invalid")
    }
    
    // Verify we can inspect the returned exception
    assertTrue(authException.message?.contains("unauthorized") == true)
}

assertFailsWith Function

Asserts that a block of code throws a specific type of exception.

/**
 * Asserts that a block fails with a specific exception of type T
 * @param message - Optional message used as prefix for failure message
 * @param block - Code block that should throw exception of type T
 * @return Exception of the expected type T
 */
inline fun <reified T : Throwable> assertFailsWith(message: String? = null, block: () -> Unit): T

/**
 * Asserts that a block fails with a specific exception of the given class
 * @param exceptionClass - Expected exception class
 * @param block - Code block that should throw the exception
 * @return Exception of the expected type T
 */
inline fun <T : Throwable> assertFailsWith(exceptionClass: KClass<T>, block: () -> Unit): T

/**
 * Asserts that a block fails with a specific exception of the given class
 * @param exceptionClass - Expected exception class
 * @param message - Optional message used as prefix for failure message  
 * @param block - Code block that should throw the exception
 * @return Exception of the expected type T
 */
inline fun <T : Throwable> assertFailsWith(exceptionClass: KClass<T>, message: String?, block: () -> Unit): T

Usage Examples:

@Test
fun testSpecificExceptions() {
    // Test specific exception type with reified generic
    val illegalArg = assertFailsWith<IllegalArgumentException> {
        validateAge(-5)
    }
    assertEquals("Age cannot be negative", illegalArg.message)
    
    // Test with custom message
    val nullPointer = assertFailsWith<NullPointerException>("Should throw NPE for null input") {
        processData(null)
    }
    
    // Test with KClass parameter
    val ioException = assertFailsWith(IOException::class) {
        readFile("nonexistent.txt")
    }
    assertContains(ioException.message ?: "", "nonexistent.txt")
}

@Test
fun testCustomExceptions() {
    class ValidationException(message: String) : Exception(message)
    
    val validationError = assertFailsWith<ValidationException> {
        validateUserInput(UserInput(name = "", email = "invalid"))
    }
    
    assertContains(validationError.message ?: "", "name")
    assertContains(validationError.message ?: "", "email")
}

@Test
fun testExceptionHierarchy() {
    // Test that we catch the specific subtype
    val specificException = assertFailsWith<FileNotFoundException> {
        openFile("missing.txt")
    }
    
    // FileNotFoundException is also an IOException
    assertTrue(specificException is IOException)
}

Expected Value Testing

expect Function

Asserts that a block returns the expected value (convenience function combining execution and assertion).

/**
 * Asserts that given function block returns the given expected value
 * @param expected - Expected return value
 * @param block - Function that should return the expected value
 */
inline fun <T> expect(expected: T, block: () -> T)

/**
 * Asserts that given function block returns the given expected value with custom message
 * @param expected - Expected return value  
 * @param message - Optional custom failure message
 * @param block - Function that should return the expected value
 */
inline fun <T> expect(expected: T, message: String?, block: () -> T)

Usage Examples:

@Test
fun testExpectedValues() {
    // Simple calculation
    expect(25) { 5 * 5 }
    
    // String operations
    expect("HELLO") { "hello".uppercase() }
    
    // With custom message
    expect(true, "User should be authenticated") { 
        authenticateUser("admin", "password123") 
    }
    
    // Complex operations
    expect(listOf(2, 4, 6)) {
        listOf(1, 2, 3).map { it * 2 }
    }
}

@Test
fun testFunctionResults() {
    // Test pure functions
    expect(42) { fibonacci(9) }  // 9th Fibonacci number
    
    // Test with side effects
    val cache = mutableMapOf<String, Int>()
    expect(5) {
        cache.computeIfAbsent("key") { 5 }
    }
    
    // Verify side effect occurred
    assertTrue(cache.containsKey("key"))
}

Advanced Exception Testing Patterns

Testing Exception Chains

@Test
fun testExceptionChaining() {
    class DatabaseException(message: String, cause: Throwable) : Exception(message, cause)
    class ServiceException(message: String, cause: Throwable) : Exception(message, cause)
    
    val serviceException = assertFailsWith<ServiceException> {
        try {
            // Simulate database failure
            throw SQLException("Connection timeout")
        } catch (e: SQLException) {
            throw DatabaseException("Database operation failed", e)
        } catch (e: DatabaseException) {
            throw ServiceException("Service unavailable", e)
        }
    }
    
    // Verify exception chain
    assertNotNull(serviceException.cause)
    assertTrue(serviceException.cause is DatabaseException)
    
    val dbException = serviceException.cause as DatabaseException
    assertNotNull(dbException.cause)
    assertTrue(dbException.cause is SQLException)
    
    val sqlException = dbException.cause as SQLException
    assertEquals("Connection timeout", sqlException.message)
}

Testing Resource Management

@Test
fun testResourceCleanup() {
    class Resource : AutoCloseable {
        var closed = false
        override fun close() { closed = true }
    }
    
    val resource = Resource()
    
    // Test that exception doesn't prevent cleanup
    assertFailsWith<IllegalStateException> {
        try {
            resource.use {
                throw IllegalStateException("Simulated error")
            }
        } finally {
            assertTrue(resource.closed, "Resource should be closed even after exception")
        }
    }
}

Testing Validation Logic

@Test
fun testInputValidation() {
    data class User(val name: String, val email: String, val age: Int)
    
    fun validateUser(user: User) {
        require(user.name.isNotBlank()) { "Name cannot be blank" }
        require(user.email.contains("@")) { "Invalid email format" }
        require(user.age >= 0) { "Age cannot be negative" }
    }
    
    // Test each validation rule
    assertFailsWith<IllegalArgumentException>("Should reject blank name") {
        validateUser(User("", "test@example.com", 25))
    }
    
    assertFailsWith<IllegalArgumentException>("Should reject invalid email") {
        validateUser(User("John", "invalid-email", 25))
    }
    
    assertFailsWith<IllegalArgumentException>("Should reject negative age") {
        validateUser(User("John", "john@example.com", -1))
    }
    
    // Test valid input doesn't throw
    assertDoesNotThrow {
        validateUser(User("John", "john@example.com", 25))
    }
}

// Helper function for readability
inline fun assertDoesNotThrow(block: () -> Unit) {
    try {
        block()
    } catch (e: Exception) {
        fail("Expected no exception, but got: ${e::class.simpleName}: ${e.message}")
    }
}

Testing Timeout and Async Operations

@Test
fun testTimeoutBehavior() {
    // Test that long-running operation times out
    val timeoutException = assertFailsWith<TimeoutException> {
        runWithTimeout(100) { // 100ms timeout
            Thread.sleep(1000) // Sleep for 1 second
        }
    }
    
    assertContains(timeoutException.message ?: "", "timeout")
}

@Test
fun testAsyncExceptions() {
    val future = CompletableFuture<String>()
    
    // Complete with exception
    future.completeExceptionally(RuntimeException("Async failure"))
    
    val executionException = assertFailsWith<ExecutionException> {
        future.get()
    }
    
    assertTrue(executionException.cause is RuntimeException)
    assertEquals("Async failure", executionException.cause?.message)
}

Testing State Transitions

@Test  
fun testStateMachine() {
    enum class State { CREATED, STARTED, STOPPED, DISPOSED }
    
    class StateMachine {
        private var state = State.CREATED
        
        fun start() {
            require(state == State.CREATED) { "Can only start from CREATED state" }
            state = State.STARTED
        }
        
        fun stop() {
            require(state == State.STARTED) { "Can only stop from STARTED state" }  
            state = State.STOPPED
        }
        
        fun dispose() {
            require(state == State.STOPPED) { "Can only dispose from STOPPED state" }
            state = State.DISPOSED
        }
    }
    
    val machine = StateMachine()
    
    // Test invalid transitions
    assertFailsWith<IllegalArgumentException>("Should not stop before starting") {
        machine.stop()
    }
    
    assertFailsWith<IllegalArgumentException>("Should not dispose before stopping") {
        machine.dispose()
    }
    
    // Test valid sequence
    machine.start()
    machine.stop()
    machine.dispose()
    
    // Test invalid transition after disposal
    assertFailsWith<IllegalArgumentException>("Should not restart after disposal") {
        machine.start()
    }
}

Test Code Utilities

todo Function

Marks unimplemented test code that should be skipped but kept referenced in the codebase.

/**
 * Takes the given block of test code and doesn't execute it
 * This keeps the code under test referenced, but doesn't actually test it until implemented
 * @param block - Test code block to skip execution
 */
fun todo(block: () -> Unit)

Usage Examples:

import kotlin.test.*

@Test
fun testComplexFeature() {
    // Test implemented part
    val basicResult = performBasicOperation()
    assertEquals("expected", basicResult)
    
    // Skip unimplemented test but keep it in code
    todo {
        // This test code won't execute but stays in the codebase
        val advancedResult = performAdvancedOperation()
        assertEquals("advanced", advancedResult)
        assertNotNull(advancedResult.metadata)
    }
}

@Test
fun testTodoWithDescription() {
    // Test current implementation
    val service = createService()
    assertTrue(service.isReady())
    
    // TODO: Add comprehensive integration tests
    todo {
        // Integration test placeholder - won't run
        val integrationResult = service.performIntegration()
        assertTrue(integrationResult.success)
        assertContains(integrationResult.logs, "Integration completed")
    }
}

class IncrementalTestDevelopment {
    @Test
    fun testBasicFunctionality() {
        // Implemented test
        val calculator = Calculator()
        assertEquals(4, calculator.add(2, 2))
    }
    
    @Test
    fun testAdvancedFunctionality() {
        todo {
            // Placeholder for future advanced tests
            val calculator = Calculator()
            assertEquals(8.0, calculator.power(2.0, 3.0), 0.001)
            assertEquals(2.0, calculator.sqrt(4.0), 0.001)
        }
    }
}

Platform Behavior:

  • JVM: Prints "TODO at [stack trace location]" to console
  • JavaScript: Prints "TODO at [function reference]" to console
  • Native/WASM: Prints "TODO at [function reference]" to console

The todo function is particularly useful during test-driven development when you want to outline test cases but implement them incrementally, ensuring unimplemented tests don't cause failures while keeping them visible in the codebase.

Types

/**
 * Represents a Kotlin class for type-safe exception assertions
 */
typealias KClass<T> = kotlin.reflect.KClass<T>

Install with Tessl CLI

npx tessl i tessl/maven-org-jetbrains-kotlin--kotlin-test

docs

annotations.md

asserter.md

basic-assertions.md

collection-assertions.md

exception-testing.md

index.md

type-null-assertions.md

tile.json