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.
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:
- Add the AssertJ Core (not mandatory) dependency to the
testImplementation
dependency configuration. - Add the JUnit Jupiter API dependency to the
testImplementation
dependency configuration. - 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
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:
- Add a new test set called
integrationTest
to our Gradle build. - Change the name of the test set's root directory from
integrationTest
tointegration-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 thetestImplementation
dependency configuration. - The
integrationTestRuntimeOnly
dependency configuration contains only the dependecies which are required when we run our integration tests. This configuration extends thetestRuntimeOnly
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:
- Ensure that JUnit 5 runs only the tests which have the tag:
unitTest
when we run our unit tests. - 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 theimplementation
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.