Property-based Testing in Kotlin: Breaking Code Faster Than You Think

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why Regular Tests Lie and You Believe Them



Imagine: you wrote a function, covered it with three unit tests. Green checkmark. CI passes. Production — a bug. Sound familiar?



The problem with classic unit tests is that they check specific examples. You come up with the input data yourself and write the expected result yourself. It's like an exam where the student writes their own questions. Sooner or later you'll miss an edge case — null, an empty list, a negative number, or a string with Unicode characters.



This is where property-based testing comes into play. Instead of writing: "if you pass 5, it should return 25", you formulate a property: "for any integer, the square is always non-negative". And the framework itself generates hundreds of random values and checks this property.



In this article, we'll explore how to use property-based testing in Kotlin with the Kotest library. You'll learn how to write reliable tests, find bugs early, and stop fearing refactoring. This isn't just boring theory — we'll write real code you can apply immediately in your project.



What is Property-based Testing and Why Kotlin?



The idea isn't new — it was popularized by the QuickCheck library for Haskell back in 1999. But only now, with the growing complexity of backend systems and microservices, property-based testing is becoming a must-have in a developer's stack.



Kotlin is the ideal language for this approach. Why?



  • Functional capabilities: lambdas, extension functions, immutability — all of this simplifies writing clean properties.
  • Kotest — a powerful framework that supports property-based tests, generators, and Shrinking (the algorithm for minimizing failing examples) out of the box.
  • Java compatibility: you can test legacy code written in Java using the same approaches.


Let's get straight to the point. Below is an example that will change your perspective on testing.



Your First Property-based Test in 5 Minutes



Let's say we have a function that checks if a string is a palindrome:



fun isPalindrome(s: String): Boolean {    val cleaned = s.filter { it.isLetterOrDigit() }.lowercase()    return cleaned == cleaned.reversed()}


A regular test looks like this:



@Testfun `test known palindromes`() {    assertTrue(isPalindrome("A man, a plan, a canal: Panama"))    assertTrue(isPalindrome("racecar"))    assertFalse(isPalindrome("hello"))}


Everything works. But what if the input data contains spaces, digits, or symbols? What if the string is empty? What if it consists only of spaces?



Now let's write a property-based test using Kotest:



import io.kotest.core.spec.style.StringSpecimport io.kotest.property.forAllimport io.kotest.property.arbitrary.string

class PalindromePropertyTest : StringSpec({ "Any string that has been cleaned should be a palindrome of itself" { forAll<String> { original -> val cleaned = original.filter { it.isLetterOrDigit() }.lowercase() isPalindrome(cleaned) == true } }})


What's happening here? forAll<String> generates random strings: empty ones, ones with spaces, with Unicode, with digits. And for each one, it checks the property. If even one string violates the assertion — the test fails, and Kotest will show the minimal reproducing example.



Even at this stage, you can find bugs. For example, if the string consists only of spaces — filter will return an empty string, and isPalindrome("") will return true. A regular test would have missed this.



Generators: Your

Blogs

Book Recommendations