Are you tired of writing tests which have a lot of boilerplate code? If so, get started with Spock Framework >>

Running Kotlin Tests With Gradle

When we start a new project with Kotlin, one of the first things that we have to do is to create a Gradle project that can compile and run our application. However, our job isn’t done yet. We still have to figure out a way to compile and run our automated tests. This blog post helps us to create a Gradle project that can compile and run both unit and integration tests which use JUnit 5.

After we have finished this blog post, we:

  • Can create a Gradle project that can compile unit and integration tests which use Kotlin.
  • Know how we can add custom test sets to our Gradle build.
  • Understand how we can declare the dependencies of our integration tests.
  • Know how we can filter the invoked tests with JUnit 5 tags.
  • Can run both unit and integration tests with Gradle.

Let’s start by taking a look at the requirements of our Gradle build.

This blog post is a free sample of my Test With Spring course. In fact, this blog post is the text version of a webinar which I presented to the people who bought the Master Package of my course.

The Requirements of Our Gradle Build

The requirements of our Gradle build are:

  • All code (application and tests) must use Kotlin.
  • Unit and integration tests must have separate source and resource directories.
  • It must be possible to run only unit or integration tests.
  • It must be possible to run both unit and integration tests.
  • If an integration test fails, our build must fail as well.
  • Integration and unit tests must have different HTML report directories.

Next, we will take a quick look at the system under test.

Introduction to System Under Test

During this blog post we will write both unit and integration tests for the MessageService class. This class has one method called getMessage() which returns the message: ‘Hello World!’.

The source code of the MessageService class looks as follows:

	
class MessageService {
 
    fun getMessage(): String {
        return "Hello World!"
    }
}

Let’s move on and find out how we can create a Gradle project that can run only unit tests.

Running Unit Tests With Gradle

When we want to create a Gradle project that can run only unit tests, we have to follow these steps:

First, because we are using JVM, we have to apply the Kotlin JVM Gradle plugin. After we have applied this plugin, our build.gradle file looks as follows:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
}

Second, we have ensure that Gradle uses the Maven central repository when it resolves the dependencies of our Gradle project. After we have configured the used repository, the source code of our build.gradle file looks as follows:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
}

repositories {
    mavenCentral()
}

Third, we have to specify the dependencies of our application. Because our application uses Kotlin, we have to add the The Kotlin standard library dependency to the implementation dependency configuration.

After we have done this, our build.gradle file looks as follows:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.jetbrains.kotlin:kotlin-stdlib:1.3.31')
}

Fourth, we have to specify the testing dependencies. Because we want to write tests for a Kotlin application by using Kotlin and JUnit 5, we can specify the testing dependencies by following these steps:

  1. Add the AssertJ Core (not mandatory) dependency to the testImplementation dependency configuration.
  2. Add the JUnit Jupiter API dependency to the testImplementation dependency configuration.
  3. Add the JUnit Jupiter Engine dependency to the testRuntime dependency configuration.

After we have specified the testing dependencies, our build.gradle file looks as follows:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.jetbrains.kotlin:kotlin-stdlib:1.3.31')
    testImplementation(
            'org.assertj:assertj-core:3.12.2',
            'org.junit.jupiter:junit-jupiter-api:5.4.2'
    )
    testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2')
}

Fifth, we have to ensure that Gradle runs our unit tests by using JUnit 5. After we have configured the test task, our build.gradle file looks as follows:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.jetbrains.kotlin:kotlin-stdlib:1.3.31')
    testImplementation(
            'org.assertj:assertj-core:3.12.2',
            'org.junit.jupiter:junit-jupiter-api:5.4.2'
    )
    testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2')
}

test {
    useJUnitPlatform()
}

We have now configured our Gradle build. However, before we can run our unit tests, we have to write one test class and put this class to the src/test/kotlin directory. The source code of our test class looks as follows:

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
 
class MessageServiceTest {
 
    private lateinit var service: MessageService
 
    @BeforeEach
    fun configureSystemUnderTest() {
        service = MessageService()
    }
 
    @Test
    @DisplayName("Should return the correct message")
    fun shouldReturnCorrectMessage() {
        val message = service.getMessage()
        assertThat(message).isEqualTo("Hello World!")
    }
}

We can now run our unit tests by using the command: gradle clean test. When we run this command, we see that Gradle compiles our application, and compiles and runs our unit tests:

> Task :clean
> Task :compileKotlin
> Task :compileJava NO-SOURCE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileTestKotlin
> Task :compileTestJava NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test
If you are using Gradle 5 and you want to see the full list of invoked tasks, you should run Gradle by using the info command line option.

Additional Reading:

So, we have just created a Gradle project that can compile and run our unit tests. However, our build doesn’t have any support for integration tests. Next, we will find out how we can support both unit and integration tests.

Running Unit and Integration Tests With Gradle

Because we want to run both unit and integration tests with Gradle, we have to make some changes to our Gradle build. We can make these changes by following these steps:

First, because we want to add new test and resource directories to our Gradle build, we have to apply the Gradle TestSets plugin. After we have applied this plugin, our build.gradle file looks as follows:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
    id 'org.unbroken-dome.test-sets' version '2.1.1'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.jetbrains.kotlin:kotlin-stdlib:1.3.31')
    testImplementation(
            'org.assertj:assertj-core:3.12.2',
            'org.junit.jupiter:junit-jupiter-api:5.4.2'
    )
    testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2')
}

Second, we have to configure the source and resource directories of our integration tests. We can configure these directories by following these steps:

  1. Add a new test set called integrationTest to our Gradle build.
  2. Change the name of the test set’s root directory from integrationTest to integration-test.

After we have configured these directories, our build.gradle file looks as follows:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
    id 'org.unbroken-dome.test-sets' version '2.1.1'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.jetbrains.kotlin:kotlin-stdlib:1.3.31')
    testImplementation(
            'org.assertj:assertj-core:3.12.2',
            'org.junit.jupiter:junit-jupiter-api:5.4.2'
    )
    testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2')
}

testSets {
    integrationTest { dirName = 'integration-test' }
}

test {
    useJUnitPlatform()
}

This configuration adds a source set called integrationTest to our Gradle build. This means that:

  • The src/integration-test/kotlin directory contains the source code of our our integration tests.
  • The src/integration-test/resources directory contains the resources of our integration tests.

Also, this configuration creates dependency configurations which allow us to declare the dependencies of our integration tests. These dependency configurations are:

  • The integrationTestImplementation dependency configuration contains the dependecies which are required to compile our integration tests. This configuration extends the testImplementation dependency configuration.
  • The integrationTestRuntimeOnly dependency configuration contains only the dependecies which are required when we run our integration tests. This configuration extends the testRuntimeOnly dependency configuration.

Third, we have to ensure that unit tests are run before integration tests. After we have added the required configuration to our build.gradle file, it looks as follows:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
    id 'org.unbroken-dome.test-sets' version '2.1.1'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.jetbrains.kotlin:kotlin-stdlib:1.3.31')
    testImplementation(
            'org.assertj:assertj-core:3.12.2',
            'org.junit.jupiter:junit-jupiter-api:5.4.2'
    )
    testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2')
}

testSets {
    integrationTest { dirName = 'integration-test' }
}

integrationTest.mustRunAfter test

test {
    useJUnitPlatform()
}

Fourth, we have to ensure that integration tests are run when we invoke the build task. Also, we must ensure that our build fails if an integration test fails. After we have added the required configuration to our build.gradle file, it looks as follows:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
    id 'org.unbroken-dome.test-sets' version '2.1.1'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.jetbrains.kotlin:kotlin-stdlib:1.3.31')
    testImplementation(
            'org.assertj:assertj-core:3.12.2',
            'org.junit.jupiter:junit-jupiter-api:5.4.2'
    )
    testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2')
}

testSets {
    integrationTest { dirName = 'integration-test' }
}

check.dependsOn integrationTest
integrationTest.mustRunAfter test

test {
    useJUnitPlatform()
}

Fifth, we have to ensure that Gradle runs our integration tests by using JUnit 5. After we have configured the integrationTest task, our build.gradle file looks as follows:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
    id 'org.unbroken-dome.test-sets' version '2.1.1'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.jetbrains.kotlin:kotlin-stdlib:1.3.31')
    testImplementation(
            'org.assertj:assertj-core:3.12.2',
            'org.junit.jupiter:junit-jupiter-api:5.4.2'
    )
    testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2')
}

testSets {
    integrationTest { dirName = 'integration-test' }
}

check.dependsOn integrationTest
integrationTest.mustRunAfter test

integrationTest {
    useJUnitPlatform()
}

test {
    useJUnitPlatform()
}

Sixth, we have to filter the invoked tests by using JUnit 5 tags. We can do this by following these steps:

  1. Ensure that JUnit 5 runs only the tests which have the tag: unitTest when we run our unit tests.
  2. Ensure that JUnit 5 runs only the tests which have the tag: integrationTest when we run our integration tests.

After we have configured the invoked tests, our build.gradle file looks as follows:

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.31'
    id 'org.unbroken-dome.test-sets' version '2.1.1'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation('org.jetbrains.kotlin:kotlin-stdlib:1.3.31')
    testImplementation(
            'org.assertj:assertj-core:3.12.2',
            'org.junit.jupiter:junit-jupiter-api:5.4.2'
    )
    testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2')
}

testSets {
    integrationTest { dirName = 'integration-test' }
}

check.dependsOn integrationTest
integrationTest.mustRunAfter test

integrationTest {
    useJUnitPlatform {
        includeTags 'integrationTest'
    }
}

test {
    useJUnitPlatform {
        includeTags 'unitTest'
    }
}

We have now created a Gradle build that can compile and run our unit and integration tests. Let’s move on and find out how we can write unit and integration tests which are run by Gradle and JUnit 5.

Writing Unit and Integration Tests

We can write our unit and integration tests by following these steps:

First, because we already wrote our unit test class, the only thing that we have to do is to add the tag: unitTest to our unit test class.

After we have added this tag to our unit test class, its source code looks as follows:

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
 
@Tag("unitTest")
class MessageServiceTest {
 
    private lateinit var service: MessageService
 
    @BeforeEach
    fun configureSystemUnderTest() {
        service = MessageService()
    }
 
    @Test
    @DisplayName("Should return the correct message")
    fun shouldReturnCorrectMessage() {
        val message = service.getMessage()
        assertThat(message).isEqualTo("Hello World!")
    }
}

Second, we have to write an integration test class which ensures that the system under test returns the correct message. Also, we must add the tag: integrationTest to our integration test class.

After we have written our integration test class, its source code looks as follows:

import org.assertj.core.api.Assertions
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
 
@Tag("integrationTest")
class GetMessageTest {
 
    private lateinit var service: MessageService
 
    @BeforeEach
    fun configureSystemUnderTest() {
        service = MessageService()
    }
 
    @Test
    @DisplayName("Should return the correct message")
    fun shouldReturnCorrectMessage() {
        val message = service.getMessage()
        Assertions.assertThat(message).isEqualTo("Hello World!")
    }
}

We have now written our unit and integration tests. Next, we will find out how we can run our tests.

Running Our Tests

If we want to run only unit tests, we have to run the command: gradle clean test. When we run this command at command prompt, we see that Gradle runs only our unit tests:

> Task :clean
> Task :compileKotlin
> Task :compileJava NO-SOURCE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileTestKotlin
> Task :compileTestJava NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test

If we want to run only integration tests, we have to run the command: gradle clean integrationTest. When we run this command at command prompt, we see that Gradle runs only our integration tests:

> Task :clean
> Task :compileKotlin
> Task :compileJava NO-SOURCE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :compileIntegrationTestKotlin
> Task :compileIntegrationTestJava NO-SOURCE
> Task :processIntegrationTestResources NO-SOURCE
> Task :integrationTestClasses UP-TO-DATE
> Task :integrationTest

If we want to run both unit and integration tests, we have to run one of these two commands: gradle clean test integrationTest or gradle clean build. When we run the build task, we see that Gradle runs both unit and integration tests:

> Task :clean
> Task :compileKotlin
> Task :compileJava NO-SOURCE
> Task :processResources NO-SOURCE
> Task :classes UP-TO-DATE
> Task :inspectClassesForKotlinIC
> Task :jar
> Task :assemble
> Task :compileTestKotlin
> Task :compileTestJava NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test
> Task :compileIntegrationTestKotlin
> Task :compileIntegrationTestJava NO-SOURCE
> Task :processIntegrationTestResources NO-SOURCE
> Task :integrationTestClasses UP-TO-DATE
> Task :integrationTest
> Task :check
> Task :build

We can now create a Gradle build that can run unit and integration tests which use Kotlin and JUnit 5. Let’s summarize what we learned from this blog post.

Summary

This blog post has taught us six things:

  • Because we are using JVM, we have to apply the Kotlin JVM Gradle plugin.
  • We have to add the Kotlin standard library (kotlin-stdlib) dependency to the implementation dependency configuration.
  • We can add new test and resource directories to our Gradle build by using the Gradle TestSets plugin.
  • The Gradle TestSets plugin creates dependency configurations which allow us to configure the dependencies of our integration tests.
  • If we want to run tests which use JUnit 5, we have to enable the Gradle’s built-in JUnit 5 support.
  • We can filter the invoked tests by using JUnit 5 tags.
If you enjoyed reading this blog post, you should take a look at my Test With Spring course because this blog post is the text version of a webinar which I presented to the people who bought the Master Package of my course.

Get the source code from Github.

1 comment… add one

    Leave a Comment