Multiplatform testing framework providing unified API for writing tests across all Kotlin platforms with assertions and test annotations.
—
Assertions for testing exception handling, failure scenarios, and expected error conditions. Essential for robust testing of error paths and edge cases.
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): NothingUsage 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)
}
}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): ThrowableUsage 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)
}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): TUsage 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)
}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"))
}@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)
}@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")
}
}
}@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}")
}
}@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)
}@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()
}
}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:
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.
/**
* 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