Testing with Kotlin and JUnit5
Tags: testing, bestpractices, tdd
Published: 2022-06-10 07:10:53

---
title: "Testing with Kotlin and JUnit5"
published: true
description: "How to get started testing in Kotlin with JUnit5? What to install? We'll also go through parameterized tests and discuss best practices here and there.In follow-up articles, we'll go through mocking and testing with Kotest."
tags: ["testing", "bestpractices", "TDD",]
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
---

## The problem

I've recently started coding backend and mobile stuff in [kotlin](https://kotlinlang.org/), following a book, then starting a pet project, ...  
In the meantime, I've also started to gain a lot of interest on **[Test Driven Development](https://www.agilest.org/devops/test-driven-development/)** and found myself wondering ***"wow, how do I test in Kotlin ? which framework should I use?"***

To learn further, I've deciced to start a serie of articles on this topic:
- **Basic testing** in Kotlin with [JUnit5](https://junit.org/junit5/docs/current/user-guide/#writing-tests) from the Java ecosystem (this article)
- **Basic testing** in Kotlin with [Kotest](https://kotest.io/) built for kotlin specificatlly (upcoming)
- **[Mocking, stubbing and contract testing](https://dzone.com/articles/how-to-test-software-part-i-mocking-stubbing-and-c)** in Kotlin with JUnit5 and Kotest (upcoming)
- A **comparision of these two testing frameworks** (mostly in term of features, usability, readability), this one might be a bit opinionated. (upcoming)

I'm a java-ist so my go-to test framework is JUnit, this is the sole reason why I'm starting by this one.

### Agenda

- [Getting started](#setup)
- [Execution and lifecycle](#tests-execution-and-orchestration)
- [A very simple test](#a-very-simple-test)
- [Checking exceptions](#what-about-checking-exceptions)
- [The power of parameterized tests](#lets-execute-the-same-test-with-different-inputs)
- [Conditional tests](#do-we-really-want-to-execute-this-test)
- [Conclusion](#conclusion)

If you just want to jump in the code, [please yourself](https://gitlab.ipsyn.net/bikette/learn-tdd/-/tree/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin)

## Basics on testing with JUnit5

### Setup

For the Gradle users, here's the setup. 

To be able to use the JUnit5 features, we must first add the dependencies:
```kotlin
  testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2")
  testImplementation("org.junit.jupiter:junit-jupiter-engine:5.8.2")
```

Then we must specify we want to use JUnit to run the tests :

```kotlin
// For Gradle + Groovy 
tasks.test {
    useJUnitPlatform()
}
// For Gradle + Kotlin 
tasks.test {
   useJUnitPlatform()
}
```
The entire necessary configuration can be found [here](https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/3f3b9ebc3cd3b7627dc023fca7a2bf4ff733dc45/money/money/build.gradle.kts).

Note: I'm using `gradle 7.3.3`.

### Tests execution and orchestration

Junit will consider as a test any function annotated with `@Test`, `@RepeatedTest`, `@ParameterizedTest`, `@TestFactory`, or `@TestTemplate`.  
We also have annotation to help us wrap test execution:
- `@BeforeAll` executed first, before the whole test suite. useful for instiantiating external dependencies for instance.
- `@BeforeEach` executed after `BeforeAll` and before any test, useful when we need to ensure the state is clean before launching for example.
- `@AfterEach` not surprisingly, executed after any test.
- `@AfterAll` executed at the end of the test suite, for housekeeping purpose, pushing stats or whatever.

### A very simple test

Now we're all set, time to code and test!  
Let's say we have a useless class like this one :

```kotlin
class Dollar(val value: Int) {
    var amount: Int = 10

    operator fun times(multiplier: Int) {
        amount = value.times(multiplier)
    }
}
```
Obviously we want to test this class has the expected behavior, with a very basic test:

We'll need to first import the JUnit5 helpers :

```kotlin
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
```

And then we can create our test as follows:

```kotlin
@Test
fun `should multiply correctly`() {
    val five = Dollar(5)
    five.times(2)
    Assertions.assertEquals(10, five.amount)
}
```
We can now execute this, using our favorite IDE or the simple gradle command :  
`./gradlew test`


#### A quick word on naming

Well, no strong rule here, but a test method should :
- test one thing, and one thing only
- have an explicit name so the expectation is clear

You may have notice that I'm using backtick here.  
This is a Kotlin capability, identifier for variable and method can use them, and although there's nothing mandatory here, I find it clearer.


### What about checking exceptions ?

Ok, now we don't want to multiply our dollar by zero, so we change our code a bit, to raise an exception if this occurs.  

Let's write the test first:
```kotlin
@Test
fun `should throw exception when multiplying by 0`(){
    val one = Dollar(1)
    assertThrows<NoMoneyException> {
        one.times(0)
    }
}
```

Yep, it's just that easy, okay, this does not even compile, since the NoMoneyException class does not exists. Let's create it !

```kotlin
class NoMoneyException(message: String?) : Throwable(message) {
}
```
We then update our `times` operator :
```kotlin
operator fun times(multiplier: Int) : Dollar {
    if (multiplier == 0) {
        throw NoMoneyException("Can't multiply by zero")
    }
    return Dollar(amount.times(multiplier))
}
```
You can run since and see the green test :)

### Let's execute the same test with different inputs !

I think you'll agree, if we add more test cases, we'll be loosing readability and we'll duplicate the same test again and again.  

#### Grouped assertions

Well there's a few great [Kotlin assertions](https://junit.org/junit5/docs/current/user-guide/#writing-tests-assertions-kotlin) that comes with Junit, let's play with `assertAll` and a collection with a multiplier and the expected result.

```kotlin
@Test
fun `should multiply using stream`() {
    val five = Dollar(5)
    val inputs = arrayListOf(
        arrayListOf(2, 10),
        arrayListOf(3, 15),
        arrayListOf(10, 50)
    )
    assertAll(
        "should provide the expected result",
        inputs
            .stream() // Remove this line and use the collection directly
            .map {
                { assertEquals(Dollar(it[1]).amount, five.times(it[0]).amount) }
            }
    )
}
```

#### Parameterized tests

Well, I've used a lot of  **[Table Driven Tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go)** in [golang](https://go.dev/), this is super helpful to write compact and repeatable tests.  
With JUnit, we can achieve the same with **Parameterized tests**.  

[Parameterized tests](https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests) makes tests more readable and avoid duplicates, but don't take my word for it, let's code !  

First, we'll need to add a new dependency :
```kotlin
   testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.2")
```

Now let's replace our [previous example](#grouped-assertions) and use a `CsvSource` with the `multiplier`  and the `expected` value :
```kotlin
@ParameterizedTest(name = "multiply {0} by 5 should return {1}")
@CsvSource(
    "2, 10",
    "3, 15",
    "10, 50",
)
fun `should multiply correctly`(multiplier: Int, expected: Int ){
    val five = Dollar(5)
    Assertions.assertEquals(Dollar(expected).amount, five.times(multiplier).amount)
}
```

So basically, we :
- add the `@ParameterizedTest` annotation and optionnally define a name,
- add the annotation for the the type of argument source we want to provide (`@CsvSource` here) with the related test cases
- enjoy ! 

You may have noticed **the name of the test**? Well, this little trick makes our test super explicit and easier to debug, see for yourself :
![test label](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/btcngepcu33k6yk0bcis.png)

With that, you can directly see which testcase fails, see for yourself: 
![Parameterized tests output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mqf23p8utoych7f160to.png)
 
I don't know about you, but I personnally find it **more readable than the group assertions**.

For more info about **customizing the display name**, see [this part](https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests-display-names) of the JUnit documentation.


Another nice thing, is that we can use several types of arguments as inputs:
- `ValueSource` allows to pass a list of arguments of primitive types, String and Class: useful for a testing a single argument. [See an example here](https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin/TestMultiplicationParameterized.kt#L30)
- `CsvSource` as show-cased here, we can pass an unlimited list of arguments as a string representing a CSV input
- `CsvFileSource` same as the previous file, except we use a CSV file. [See an example here](https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin/TestMultiplicationParameterized.kt#L47)
- `EnumSource` lets you run the same test for each constant (or a selected set) of a given ENUM. [See an example here](https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin/TestMultiplicationParameterized.kt#L54)
- `MethodSource` this one is super powerful, we can basically have anything we want as input source (say a JSON or a Parquet file), process it with the `MethodSource` and use it to execute our tests. [See an example here](https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin/TestMultiplicationParameterized.kt#L75)
- `ArgumentSource` this one goes even further than `MethodSource`. With a new class, implementing the `ArgumentProvider` interface, you can generate input data. [See an example here](https://gitlab.ipsyn.net/bikette/learn-tdd/-/blob/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin/TestMultiplicationParameterized.kt#L84)

We also have a bit of syntaxic sugar with `@NullSource`, `@EmptySource` and `@NullAndEmptySource`.

> #### Be careful when using BeforeEach and "parameterized" or "repeated" tests
>
> Each item of a parameterized or repeated test suite is considered as a single test.  
> Therefore, whatever is defined in `BeforeEach` will be executed before each occurence of the test source.  


Say we define a `BeforeEach` as follows :
```kotlin
@BeforeEach
fun initEach() {
    println("hello I'm a test")
}
```
After executing `should multiply correctly` which have 3 rows in its `CsvSource`, we'll have the following output:
```text
hello I'm a test
hello I'm a test
hello I'm a test
```

### Do we really want to execute this test ?

JUnit comes with multiple way to decide whether a test sould be executed or not, depending of the context.  
First we can totally disable a test or a class, by adding the `@Disabled` annotation.  

In addition, we can programmatically define condition execution depending on:
- the **Operating System**, using `@EnabledOnOs`, `@DisabledOnOs` and the `OS` Enum:
```kotlin
@EnabledOnOs(OS.MAC)
@EnabledOnOs(OS.MAC, OS.LINUX)
```
- the **JRE**, with `@EnabledOnJre`, `@EnabledForJreRange`, `@DisabledOnJre`, `@DisabledForJreRange` and the  `JRE` Enum:
```kotlin
@EnabledOnJre(JAVA_8)
@EnabledForJreRange(min = JRE.JAVA_11, max = JRE.JAVA_16)
```
- **system properties**, with `@EnabledIfSystemProperty` and `@DisabledIfSystemProperty`
```kotlin
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
```
- one or multiple **environment variable(s)**, `@EnabledIfEnvironmentVariable`, `@EnabledIfEnvironmentVariables`, `@DisabledIfEnvironmentVariable` and `@DisabledIfEnvironmentVariable`:
```kotlin
@EnabledIfEnvironmentVariable(named = "EXEC_ENV", matches = ".*ci.*")
@EnabledIfEnvironmentVariable(named = "DEBUG", matches = "enabled")
```
Note that the two singular annotations are repeatable.

- a **custom condition***, using `@EnabledIf` and `@DisabledIf` with a method name or its FQN (if the method is not in the same class) as string:
```kotlin
@EnabledIf("execAlways")
@Test
fun `something to test`(){}

private fun execAlways(): Boolean {
    return true
}
```

## Conclusion

That's it for this first part, covering the basics on testing Kotlin code with JUnit, we can already check a lot of things with :
- grouped assertions
- exception check
- parameterized tests
- conditional tests

I hope you'll find this useful, you can find the full test implementation [here](https://gitlab.ipsyn.net/bikette/learn-tdd/-/tree/8d036da4e71709c56d0be96265881fe95f6da896/money/money/src/test/kotlin).