0
# Data-Driven Testing
1
2
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.
3
4
## Capabilities
5
6
### Row Definitions
7
8
Type-safe row containers for organizing test data. Each row type corresponds to a specific number of columns.
9
10
```kotlin { .api }
11
/**
12
* Base interface for all row types
13
*/
14
interface Row {
15
fun values(): List<Any?>
16
}
17
18
/**
19
* Single-column row
20
*/
21
data class Row1<out A>(val a: A) : Row
22
23
/**
24
* Two-column row
25
*/
26
data class Row2<out A, out B>(val a: A, val b: B) : Row
27
28
/**
29
* Three-column row
30
*/
31
data class Row3<out A, out B, out C>(val a: A, val b: B, val c: C) : Row
32
33
// Continues through Row22<A, B, C, ..., V> for up to 22 columns
34
```
35
36
### Row Factory Functions
37
38
Convenient functions for creating rows with automatic type inference.
39
40
```kotlin { .api }
41
/**
42
* Create a single-column row
43
*/
44
fun <A> row(a: A): Row1<A>
45
46
/**
47
* Create a two-column row
48
*/
49
fun <A, B> row(a: A, b: B): Row2<A, B>
50
51
/**
52
* Create a three-column row
53
*/
54
fun <A, B, C> row(a: A, b: B, c: C): Row3<A, B, C>
55
56
// Continues through row(a, b, ..., v) for up to 22 parameters
57
```
58
59
### Table Headers
60
61
Type-safe header definitions that provide column labels for tables.
62
63
```kotlin { .api }
64
/**
65
* Single-column headers
66
*/
67
data class Headers1(val labelA: String)
68
69
/**
70
* Two-column headers
71
*/
72
data class Headers2(val labelA: String, val labelB: String)
73
74
/**
75
* Three-column headers
76
*/
77
data class Headers3(val labelA: String, val labelB: String, val labelC: String)
78
79
// Continues through Headers22 for up to 22 columns
80
```
81
82
### Header Factory Functions
83
84
Convenient functions for creating headers.
85
86
```kotlin { .api }
87
/**
88
* Create single-column headers
89
*/
90
fun headers(a: String): Headers1
91
92
/**
93
* Create two-column headers
94
*/
95
fun headers(a: String, b: String): Headers2
96
97
/**
98
* Create three-column headers
99
*/
100
fun headers(a: String, b: String, c: String): Headers3
101
102
// Continues through headers(a, b, ..., v) for up to 22 parameters
103
```
104
105
### Table Definitions
106
107
Type-safe table containers that combine headers with rows of data.
108
109
```kotlin { .api }
110
/**
111
* Single-column table
112
*/
113
data class Table1<out A>(val headers: Headers1, val rows: List<Row1<A>>)
114
115
/**
116
* Two-column table
117
*/
118
data class Table2<out A, out B>(val headers: Headers2, val rows: List<Row2<A, B>>)
119
120
/**
121
* Three-column table
122
*/
123
data class Table3<out A, out B, out C>(val headers: Headers3, val rows: List<Row3<A, B, C>>)
124
125
// Continues through Table22 for up to 22 columns
126
```
127
128
### Table Factory Functions
129
130
Functions for creating tables from headers and rows.
131
132
```kotlin { .api }
133
/**
134
* Create table from headers and row list
135
*/
136
fun <A> table(headers: Headers1, rows: List<Row1<A>>): Table1<A>
137
138
/**
139
* Create table from headers and vararg rows
140
*/
141
fun <A> table(headers: Headers1, vararg rows: Row1<A>): Table1<A>
142
143
/**
144
* Create two-column table from headers and row list
145
*/
146
fun <A, B> table(headers: Headers2, rows: List<Row2<A, B>>): Table2<A, B>
147
148
/**
149
* Create two-column table from headers and vararg rows
150
*/
151
fun <A, B> table(headers: Headers2, vararg rows: Row2<A, B>): Table2<A, B>
152
153
// Similar patterns continue for all table arities through Table22
154
```
155
156
### Table Testing Functions
157
158
Execute tests against all rows in a table with full type safety.
159
160
```kotlin { .api }
161
/**
162
* Execute test function for each row in single-column table
163
*/
164
suspend fun <A> Table1<A>.forAll(fn: suspend (A) -> Unit)
165
166
/**
167
* Execute test function for each row in two-column table
168
*/
169
suspend fun <A, B> Table2<A, B>.forAll(fn: suspend (A, B) -> Unit)
170
171
/**
172
* Execute test function for each row in three-column table
173
*/
174
suspend fun <A, B, C> Table3<A, B, C>.forAll(fn: suspend (A, B, C) -> Unit)
175
176
// Continues through Table9.forAll for up to 9-parameter test functions
177
178
/**
179
* Assert that no rows in single-column table match the predicate
180
*/
181
suspend fun <A> Table1<A>.forNone(fn: suspend (A) -> Unit)
182
183
/**
184
* Assert that no rows in two-column table match the predicate
185
*/
186
suspend fun <A, B> Table2<A, B>.forNone(fn: suspend (A, B) -> Unit)
187
188
// Similar patterns for forNone with other table arities
189
```
190
191
### Standalone Testing Functions
192
193
Execute table tests without using extension functions.
194
195
```kotlin { .api }
196
/**
197
* Test all rows in a single-column table
198
*/
199
suspend fun <A> forAll(table: Table1<A>, fn: suspend (A) -> Unit)
200
201
/**
202
* Test all rows in a two-column table
203
*/
204
suspend fun <A, B> forAll(table: Table2<A, B>, fn: suspend (A, B) -> Unit)
205
206
// Continues through forAll for up to 9-parameter variants
207
208
/**
209
* Assert no rows in single-column table match
210
*/
211
suspend fun <A> forNone(table: Table1<A>, fn: suspend (A) -> Unit)
212
213
/**
214
* Assert no rows in two-column table match
215
*/
216
suspend fun <A, B> forNone(table: Table2<A, B>, fn: suspend (A, B) -> Unit)
217
218
// Similar patterns for forNone with other arities
219
```
220
221
### Vararg Testing Functions
222
223
Execute tests directly with row data without creating tables first.
224
225
```kotlin { .api }
226
/**
227
* Test all provided single-column rows
228
*/
229
suspend fun <A> forAll(vararg rows: Row1<A>, testfn: suspend (A) -> Unit)
230
231
/**
232
* Test all provided two-column rows
233
*/
234
suspend fun <A, B> forAll(vararg rows: Row2<A, B>, testfn: suspend (A, B) -> Unit)
235
236
/**
237
* Test all provided three-column rows
238
*/
239
suspend fun <A, B, C> forAll(vararg rows: Row3<A, B, C>, testfn: suspend (A, B, C) -> Unit)
240
241
// Continues through forAll for up to 22-parameter variants
242
243
/**
244
* Assert that none of the provided single-column rows match
245
*/
246
suspend fun <A> forNone(vararg rows: Row1<A>, testfn: suspend (A) -> Unit)
247
248
/**
249
* Assert that none of the provided two-column rows match
250
*/
251
suspend fun <A, B> forNone(vararg rows: Row2<A, B>, testfn: suspend (A, B) -> Unit)
252
253
// Similar patterns for forNone with other arities
254
```
255
256
## Usage Examples
257
258
### Basic Table Testing
259
260
```kotlin
261
import io.kotest.data.*
262
import io.kotest.matchers.shouldBe
263
264
// Create and test a simple table
265
val mathTable = table(
266
headers("a", "b", "sum"),
267
row(1, 2, 3),
268
row(3, 4, 7),
269
row(5, 6, 11),
270
row(10, 20, 30)
271
)
272
273
mathTable.forAll { a, b, expected ->
274
(a + b) shouldBe expected
275
}
276
```
277
278
### String Processing Table
279
280
```kotlin
281
import io.kotest.data.*
282
import io.kotest.matchers.shouldBe
283
284
val stringTable = table(
285
headers("input", "expected_length", "expected_uppercase"),
286
row("hello", 5, "HELLO"),
287
row("world", 5, "WORLD"),
288
row("kotlin", 6, "KOTLIN"),
289
row("", 0, "")
290
)
291
292
stringTable.forAll { input, expectedLength, expectedUpper ->
293
input.length shouldBe expectedLength
294
input.uppercase() shouldBe expectedUpper
295
}
296
```
297
298
### Single Column Testing
299
300
```kotlin
301
import io.kotest.data.*
302
import io.kotest.matchers.shouldBe
303
304
val numbersTable = table(
305
headers("number"),
306
row(2),
307
row(4),
308
row(6),
309
row(8)
310
)
311
312
numbersTable.forAll { number ->
313
(number % 2) shouldBe 0 // Assert all numbers are even
314
}
315
```
316
317
### Complex Data Types
318
319
```kotlin
320
import io.kotest.data.*
321
import io.kotest.matchers.shouldBe
322
323
data class User(val name: String, val age: Int)
324
data class ValidationResult(val isValid: Boolean, val errors: List<String>)
325
326
val userValidationTable = table(
327
headers("user", "expected_result"),
328
row(
329
User("Alice", 25),
330
ValidationResult(true, emptyList())
331
),
332
row(
333
User("", 25),
334
ValidationResult(false, listOf("Name cannot be empty"))
335
),
336
row(
337
User("Bob", -1),
338
ValidationResult(false, listOf("Age must be positive"))
339
)
340
)
341
342
userValidationTable.forAll { user, expected ->
343
val result = validateUser(user) // Hypothetical validation function
344
result shouldBe expected
345
}
346
```
347
348
### Using forNone
349
350
```kotlin
351
import io.kotest.data.*
352
import io.kotest.matchers.shouldBe
353
354
val invalidInputsTable = table(
355
headers("invalid_input"),
356
row(""),
357
row(" "),
358
row("\t\n"),
359
row(null)
360
)
361
362
// Assert that none of these inputs are valid
363
invalidInputsTable.forNone { input ->
364
isValidInput(input) shouldBe true // This should fail for all rows
365
}
366
```
367
368
### Large Tables with Many Columns
369
370
```kotlin
371
import io.kotest.data.*
372
import io.kotest.matchers.shouldBe
373
374
// Example with 5 columns
375
val productTable = table(
376
headers("name", "price", "category", "inStock", "rating"),
377
row("Laptop", 999.99, "Electronics", true, 4.5),
378
row("Book", 19.99, "Literature", true, 4.2),
379
row("Phone", 699.99, "Electronics", false, 4.8)
380
)
381
382
productTable.forAll { name, price, category, inStock, rating ->
383
name.isNotEmpty() shouldBe true
384
price shouldBe greaterThan(0.0)
385
category.isNotEmpty() shouldBe true
386
rating shouldBe between(0.0, 5.0)
387
}
388
```
389
390
### Using Vararg Testing Functions
391
392
```kotlin
393
import io.kotest.data.*
394
import io.kotest.matchers.shouldBe
395
396
// Test directly with rows without creating a table first
397
forAll(
398
row("apple", 5),
399
row("banana", 6),
400
row("cherry", 6),
401
row("date", 4)
402
) { fruit, expectedLength ->
403
fruit.length shouldBe expectedLength
404
}
405
406
// Test that none of these calculations are correct
407
forNone(
408
row(2, 2, 5), // 2 + 2 ≠ 5
409
row(3, 3, 7), // 3 + 3 ≠ 7
410
row(4, 4, 9) // 4 + 4 ≠ 9
411
) { a, b, wrongSum ->
412
(a + b) shouldBe wrongSum // All should fail
413
}
414
415
// Single column vararg testing
416
forAll(
417
row(2),
418
row(4),
419
row(6),
420
row(8)
421
) { number ->
422
(number % 2) shouldBe 0 // All should be even
423
}
424
```
425
426
## Type Safety Benefits
427
428
The data-driven testing framework maintains full type safety throughout:
429
430
```kotlin
431
// Type inference works automatically
432
val table = table(
433
headers("number", "text"),
434
row(42, "hello"), // Inferred as Table2<Int, String>
435
row(100, "world")
436
)
437
438
table.forAll { number, text ->
439
// number is Int, text is String - fully typed
440
number + text.length // Compiles correctly
441
}
442
```
443
444
## Performance Considerations
445
446
- **Suspend Functions**: All testing functions are suspending to support async operations
447
- **Type Erasure**: Tables maintain runtime type information where possible
448
- **Memory Efficiency**: Large tables are processed row-by-row, not loaded entirely into memory
449
- **Parallel Execution**: Individual row tests can be parallelized when appropriate