Kotest Property Test Functions
forAll and checkAll.For All
First, forAll receives an n-arity function (a, ..., n) -> Boolean that tests a property. If the function returns true for every input value, the test passes.
import io.kotest.core.spec.style.StringSpec
import io.kotest.property.forAll
class PropertyExample: StringSpec({
"String size" {
forAll<String, String> { a, b ->
(a + b).length == a.length + b.length
}
}
})
This function accepts type parameters for the argument types and supports arities up to 14. Kotest uses these type parameters to find generators that provide, or generate, random values of the appropriate type.
For example, forAll<String, Int, Boolean> { a, b, c -> } is a 3-arity property test where argument a is a random string, argument b is a random int, and argument c is a random boolean.
Check All
Second, checkAll receives an n-arity function (a, ..., n) -> Unit that can simply run assertions against the inputs. With this approach, the test is considered valid if no exception is thrown. The following rewrites the same example in the same way using checkAll.
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.string.shouldHaveLength
import io.kotest.property.checkAll
class PropertyExample: StringSpec({
"String size" {
checkAll<String, String> { a, b ->
a + b shouldHaveLength a.length + b.length
}
}
})
The second approach is more general-purpose than returning a boolean, while the first approach comes from the original Haskell library that inspired this library.
Iterations
By default, Kotest runs a property test 1000 times. You can easily customize this by specifying the number of iterations when calling the test method.
Suppose you want to run the test 10,000 times.
import io.kotest.core.spec.style.StringSpec
import io.kotest.property.checkAll
class PropertyExample: StringSpec({
"a many iterations test" {
checkAll<Double, Double>(10_000) { a, b ->
// test here
}
}
})
Specifying Generators
In the previous example, Kotest automatically provided values based on the type parameters. It does this by finding a generator that creates values for the required type. For example, the automatically provided integer generator creates random integers from all possible values such as negative values, positive values, infinity, and zero.
This is suitable for basic tests, but you often want more control over the sample space. For example, you might want to test a function only for numbers in a specific range. In that case, you should specify generators manually.
import io.kotest.core.spec.style.StringSpec
import io.kotest.property.Arb
import io.kotest.property.arbitrary.int
import io.kotest.property.forAll
class PropertyExample4 : StringSpec({
fun isDrinkingAge(a: Int): Boolean =
a in 19..Int.MAX_VALUE
"is allowed to drink in Chicago" {
forAll(Arb.int(21..150)) { a ->
isDrinkingAge(a) // assuming some function that calculates if we're old enough to drink
}
}
"is allowed to drink in London" {
forAll(Arb.int(18..150)) { a ->
isDrinkingAge(a) // assuming some function that calculates if we're old enough to drink
}
}
})
You can see that two tests are created and each passes a generator with the appropriate int range to the forAll function.
See here for the list of built-in generators.