kotlinintermediate

Testing with JUnit 5 and Kotlin

Write expressive tests in Kotlin: JUnit 5, nested tests, parameterized tests, and extension functions.

kotlin
import org.junit.jupiter.api.*
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream

// Class under test
class Calculator {
    fun add(a: Int, b: Int) = a + b
    fun divide(a: Int, b: Int): Double {
        require(b != 0) { "Cannot divide by zero" }
        return a.toDouble() / b
    }
    fun isEven(n: Int) = n % 2 == 0
}

class CalculatorTest {
    private lateinit var calc: Calculator

    @BeforeEach
    fun setup() {
        calc = Calculator()
    }

    @Test
    fun `add should sum two numbers`() {
        assertEquals(4, calc.add(2, 2))
        assertEquals(0, calc.add(-1, 1))
        assertEquals(-3, calc.add(-1, -2))
    }

    @Test
    fun `divide should return correct result`() {
        assertEquals(2.5, calc.divide(5, 2))
    }

    @Test
    fun `divide by zero should throw`() {
        val exception = assertThrows<IllegalArgumentException> {
            calc.divide(1, 0)
        }
        assertTrue(exception.message!!.contains("zero"))
    }

    @Nested
    @DisplayName("Parameterized Tests")
    inner class Parameterized {
        @ParameterizedTest(name = "{0} + {1} = {2}")
        @CsvSource(
            "1, 1, 2",
            "2, 3, 5",
            "10, -5, 5",
            "0, 0, 0"
        )
        fun `add with parameters`(a: Int, b: Int, expected: Int) {
            assertEquals(expected, calc.add(a, b))
        }

        @ParameterizedTest
        @MethodSource("evenNumbers")
        fun `isEven should return true for even numbers`(n: Int) {
            assertTrue(calc.isEven(n))
        }

        companion object {
            @JvmStatic
            fun evenNumbers(): Stream<Int> = Stream.of(0, 2, 4, 100, -2)
        }
    }

    @Nested
    inner class EdgeCases {
        @Test
        fun `add with max int`() {
            // Check overflow behavior
            val result = calc.add(Int.MAX_VALUE, 1)
            assertTrue(result < 0) // overflow
        }
    }
}

// Custom assertions via extension functions
fun <T> T.shouldEqual(expected: T) {
    assertEquals(expected, this)
}

fun <T> T.shouldNotBeNull(): T {
    assertNotNull(this)
    return this
}

fun (() -> Unit).shouldThrow<T : Throwable>(): T {
    return assertThrows(T::class.java) { this() }
}

infix fun <T> T.shouldBe(expected: T) = assertEquals(expected, this)

// Usage of custom assertions
class CustomAssertionTest {
    @Test
    fun `custom assertions demo`() {
        val calc = Calculator()
        calc.add(2, 3) shouldBe 5
        calc.divide(10, 4).shouldEqual(2.5)
        "hello".shouldNotBeNull()
    }
}

Use Cases

  • Unit testing Kotlin classes and functions
  • Parameterized test suites
  • Custom assertion DSLs for readable tests

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.