Property-based Testing
Kotest is split into several subprojects which can be used independently. One of these subprojects is the property test framework. You do not need to be using Kotest as your test framework (although you should!) to benefit from the property test support.
What is Property Testing?​
Developers typically write example-based tests. These are your garden variety unit tests you know and love. You provide some inputs, and some expected results, and a test framework like Kotest or JUnit checks that the actual results meet the expectations.
One problem with this approach is that it is very easy to miss errors due to edge cases that the developer didn't think about, or lack of coverage in the chosen inputs. Instead, with property testing, hundreds or thousands of values are fed into the same test, and the values are (usually) randomly generated by your property test framework.
For example, a good property test framework will include values like negative infinity, empty lists, strings with non-ascii characters, and so on. Things we often forget about when writing example based tests.
Property tests were originally conceived in frameworks like Quickcheck with the notion of testing a property on some object, ie. something that should hold true for all inputs. An invariant in other words. An example of an invariant is given two strings, a and b, then length(a + b) should always be equal to length(a) + length(b).
That is where the term property testing originates.
However, you do not have to use property tests to only test things like monad laws or basic numeric functions. Any test that would benefit from a wide array of input values is a good candidate. For example, we might have a function that validates usernames, and we want to test that valid emails are accepted. A property test would be useful here in generating 1000s of combinations to help harden our validation logic.
Getting Started​
The property test framework is supported on all targets.
- JVM/Gradle
- JVM/Maven
- Multiplatform
Add the following dependency to your build:
dependencies {
testImplementation("io.kotest:kotest-property:$version")
}
Add the following dependency to your build.
<dependency>
<groupId>io.kotest</groupId>
<artifactId>kotest-property-jvm</artifactId>
<version>${version}</version>
<scope>test</scope>
</dependency>
Add the following dependency to your commonTest
sourceset:
kotlin {
sourceSets {
val commonTest by getting {
dependencies {
implementation("io.kotest:kotest-property:$version")
}
}
}
}
Alternatively, add the dependency to a specific target.
For example, we could add to the Javascript target only be using the jsTest
sourceset.
kotlin {
targets {
js {
browser()
nodejs()
}
}
sourceSets {
val jsTest by getting {
dependencies {
implementation("io.kotest:kotest-property:$version")
}
}
}
}
Next Steps​
To create input values for tests, Kotest uses the term generator. One generator per input argument is passed to a test function, and the test will execute for a set number of iterations.
Read more about
- How test functions are used.
- The different types of generators and operations on them.
- How to write a custom generator.
- How to specify config for a test, including the seed.