Core assertion building blocks for Kotest testing framework providing foundational utilities like shouldBe for all platforms
—
The data-driven testing framework provides comprehensive table-based testing capabilities with full type safety. It supports up to 22-column tables with strongly-typed row definitions and automatic test execution across all data combinations.
Type-safe row containers for organizing test data. Each row type corresponds to a specific number of columns.
/**
* Base interface for all row types
*/
interface Row {
fun values(): List<Any?>
}
/**
* Single-column row
*/
data class Row1<out A>(val a: A) : Row
/**
* Two-column row
*/
data class Row2<out A, out B>(val a: A, val b: B) : Row
/**
* Three-column row
*/
data class Row3<out A, out B, out C>(val a: A, val b: B, val c: C) : Row
// Continues through Row22<A, B, C, ..., V> for up to 22 columnsConvenient functions for creating rows with automatic type inference.
/**
* Create a single-column row
*/
fun <A> row(a: A): Row1<A>
/**
* Create a two-column row
*/
fun <A, B> row(a: A, b: B): Row2<A, B>
/**
* Create a three-column row
*/
fun <A, B, C> row(a: A, b: B, c: C): Row3<A, B, C>
// Continues through row(a, b, ..., v) for up to 22 parametersType-safe header definitions that provide column labels for tables.
/**
* Single-column headers
*/
data class Headers1(val labelA: String)
/**
* Two-column headers
*/
data class Headers2(val labelA: String, val labelB: String)
/**
* Three-column headers
*/
data class Headers3(val labelA: String, val labelB: String, val labelC: String)
// Continues through Headers22 for up to 22 columnsConvenient functions for creating headers.
/**
* Create single-column headers
*/
fun headers(a: String): Headers1
/**
* Create two-column headers
*/
fun headers(a: String, b: String): Headers2
/**
* Create three-column headers
*/
fun headers(a: String, b: String, c: String): Headers3
// Continues through headers(a, b, ..., v) for up to 22 parametersType-safe table containers that combine headers with rows of data.
/**
* Single-column table
*/
data class Table1<out A>(val headers: Headers1, val rows: List<Row1<A>>)
/**
* Two-column table
*/
data class Table2<out A, out B>(val headers: Headers2, val rows: List<Row2<A, B>>)
/**
* Three-column table
*/
data class Table3<out A, out B, out C>(val headers: Headers3, val rows: List<Row3<A, B, C>>)
// Continues through Table22 for up to 22 columnsFunctions for creating tables from headers and rows.
/**
* Create table from headers and row list
*/
fun <A> table(headers: Headers1, rows: List<Row1<A>>): Table1<A>
/**
* Create table from headers and vararg rows
*/
fun <A> table(headers: Headers1, vararg rows: Row1<A>): Table1<A>
/**
* Create two-column table from headers and row list
*/
fun <A, B> table(headers: Headers2, rows: List<Row2<A, B>>): Table2<A, B>
/**
* Create two-column table from headers and vararg rows
*/
fun <A, B> table(headers: Headers2, vararg rows: Row2<A, B>): Table2<A, B>
// Similar patterns continue for all table arities through Table22Execute tests against all rows in a table with full type safety.
/**
* Execute test function for each row in single-column table
*/
suspend fun <A> Table1<A>.forAll(fn: suspend (A) -> Unit)
/**
* Execute test function for each row in two-column table
*/
suspend fun <A, B> Table2<A, B>.forAll(fn: suspend (A, B) -> Unit)
/**
* Execute test function for each row in three-column table
*/
suspend fun <A, B, C> Table3<A, B, C>.forAll(fn: suspend (A, B, C) -> Unit)
// Continues through Table9.forAll for up to 9-parameter test functions
/**
* Assert that no rows in single-column table match the predicate
*/
suspend fun <A> Table1<A>.forNone(fn: suspend (A) -> Unit)
/**
* Assert that no rows in two-column table match the predicate
*/
suspend fun <A, B> Table2<A, B>.forNone(fn: suspend (A, B) -> Unit)
// Similar patterns for forNone with other table aritiesExecute table tests without using extension functions.
/**
* Test all rows in a single-column table
*/
suspend fun <A> forAll(table: Table1<A>, fn: suspend (A) -> Unit)
/**
* Test all rows in a two-column table
*/
suspend fun <A, B> forAll(table: Table2<A, B>, fn: suspend (A, B) -> Unit)
// Continues through forAll for up to 9-parameter variants
/**
* Assert no rows in single-column table match
*/
suspend fun <A> forNone(table: Table1<A>, fn: suspend (A) -> Unit)
/**
* Assert no rows in two-column table match
*/
suspend fun <A, B> forNone(table: Table2<A, B>, fn: suspend (A, B) -> Unit)
// Similar patterns for forNone with other aritiesExecute tests directly with row data without creating tables first.
/**
* Test all provided single-column rows
*/
suspend fun <A> forAll(vararg rows: Row1<A>, testfn: suspend (A) -> Unit)
/**
* Test all provided two-column rows
*/
suspend fun <A, B> forAll(vararg rows: Row2<A, B>, testfn: suspend (A, B) -> Unit)
/**
* Test all provided three-column rows
*/
suspend fun <A, B, C> forAll(vararg rows: Row3<A, B, C>, testfn: suspend (A, B, C) -> Unit)
// Continues through forAll for up to 22-parameter variants
/**
* Assert that none of the provided single-column rows match
*/
suspend fun <A> forNone(vararg rows: Row1<A>, testfn: suspend (A) -> Unit)
/**
* Assert that none of the provided two-column rows match
*/
suspend fun <A, B> forNone(vararg rows: Row2<A, B>, testfn: suspend (A, B) -> Unit)
// Similar patterns for forNone with other aritiesimport io.kotest.data.*
import io.kotest.matchers.shouldBe
// Create and test a simple table
val mathTable = table(
headers("a", "b", "sum"),
row(1, 2, 3),
row(3, 4, 7),
row(5, 6, 11),
row(10, 20, 30)
)
mathTable.forAll { a, b, expected ->
(a + b) shouldBe expected
}import io.kotest.data.*
import io.kotest.matchers.shouldBe
val stringTable = table(
headers("input", "expected_length", "expected_uppercase"),
row("hello", 5, "HELLO"),
row("world", 5, "WORLD"),
row("kotlin", 6, "KOTLIN"),
row("", 0, "")
)
stringTable.forAll { input, expectedLength, expectedUpper ->
input.length shouldBe expectedLength
input.uppercase() shouldBe expectedUpper
}import io.kotest.data.*
import io.kotest.matchers.shouldBe
val numbersTable = table(
headers("number"),
row(2),
row(4),
row(6),
row(8)
)
numbersTable.forAll { number ->
(number % 2) shouldBe 0 // Assert all numbers are even
}import io.kotest.data.*
import io.kotest.matchers.shouldBe
data class User(val name: String, val age: Int)
data class ValidationResult(val isValid: Boolean, val errors: List<String>)
val userValidationTable = table(
headers("user", "expected_result"),
row(
User("Alice", 25),
ValidationResult(true, emptyList())
),
row(
User("", 25),
ValidationResult(false, listOf("Name cannot be empty"))
),
row(
User("Bob", -1),
ValidationResult(false, listOf("Age must be positive"))
)
)
userValidationTable.forAll { user, expected ->
val result = validateUser(user) // Hypothetical validation function
result shouldBe expected
}import io.kotest.data.*
import io.kotest.matchers.shouldBe
val invalidInputsTable = table(
headers("invalid_input"),
row(""),
row(" "),
row("\t\n"),
row(null)
)
// Assert that none of these inputs are valid
invalidInputsTable.forNone { input ->
isValidInput(input) shouldBe true // This should fail for all rows
}import io.kotest.data.*
import io.kotest.matchers.shouldBe
// Example with 5 columns
val productTable = table(
headers("name", "price", "category", "inStock", "rating"),
row("Laptop", 999.99, "Electronics", true, 4.5),
row("Book", 19.99, "Literature", true, 4.2),
row("Phone", 699.99, "Electronics", false, 4.8)
)
productTable.forAll { name, price, category, inStock, rating ->
name.isNotEmpty() shouldBe true
price shouldBe greaterThan(0.0)
category.isNotEmpty() shouldBe true
rating shouldBe between(0.0, 5.0)
}import io.kotest.data.*
import io.kotest.matchers.shouldBe
// Test directly with rows without creating a table first
forAll(
row("apple", 5),
row("banana", 6),
row("cherry", 6),
row("date", 4)
) { fruit, expectedLength ->
fruit.length shouldBe expectedLength
}
// Test that none of these calculations are correct
forNone(
row(2, 2, 5), // 2 + 2 ≠ 5
row(3, 3, 7), // 3 + 3 ≠ 7
row(4, 4, 9) // 4 + 4 ≠ 9
) { a, b, wrongSum ->
(a + b) shouldBe wrongSum // All should fail
}
// Single column vararg testing
forAll(
row(2),
row(4),
row(6),
row(8)
) { number ->
(number % 2) shouldBe 0 // All should be even
}The data-driven testing framework maintains full type safety throughout:
// Type inference works automatically
val table = table(
headers("number", "text"),
row(42, "hello"), // Inferred as Table2<Int, String>
row(100, "world")
)
table.forAll { number, text ->
// number is Int, text is String - fully typed
number + text.length // Compiles correctly
}Install with Tessl CLI
npx tessl i tessl/maven-io-kotest--kotest-assertions-shared-jvm