Testing in Kotlin: unit, integration, e2e — a complete guide

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

Introduction to Testing in Kotlin: From Unit Tests to End-to-End

Testing is an integral part of developing reliable software. In the Kotlin ecosystem, which is actively used for Android development, backend on Ktor, and multiplatform projects, there is a powerful toolkit for all testing levels. In this article, we will break down three key levels: unit tests, integration tests, and end-to-end (e2e) tests. You will learn how they differ, when to apply them, and how to write effective tests in Kotlin with code examples.



Unit Testing in Kotlin: The Foundation of Reliability

Unit tests verify the operation of individual components (functions, classes) in isolation. In Kotlin, the de facto standard is the JUnit 5 framework combined with the mocking library MockK.



Unit Test Example with JUnit 5 and MockK

Consider a simple service for calculating a discount:

class DiscountService(private val userRepository: UserRepository) {    fun calculateDiscount(userId: String, amount: Double): Double {        val user = userRepository.findById(userId) ?: return amount        return if (user.isPremium) amount * 0.9 else amount    }}

Test for this service:

import io.mockk.everyimport io.mockk.mockkimport org.junit.jupiter.api.Testimport kotlin.test.assertEquals

class DiscountServiceTest {

@Test fun `should apply 10% discount for premium user`() { // Arrange val userRepo = mockk<UserRepository>() every { userRepo.findById("123") } returns User("123", isPremium = true) val service = DiscountService(userRepo)

// Act val result = service.calculateDiscount("123", 100.0)

// Assert assertEquals(90.0, result) }

@Test fun `should not apply discount for regular user`() { val userRepo = mockk<UserRepository>() every { userRepo.findById("456") } returns User("456", isPremium = false) val service = DiscountService(userRepo)

val result = service.calculateDiscount("456", 100.0)

assertEquals(100.0, result) }}

Key points:

  • Use mockk{} to create mocks.
  • The every { ... } returns ... block defines mock behavior.
  • The Arrange-Act-Assert structure makes tests readable.
  • Unit tests should run quickly and not depend on external resources.


Integration Testing: Checking Component Interaction

Integration tests verify how multiple modules work together — for example, a service with a real database or external API. For Kotlin, Testcontainers (for Docker containers) and Spring Boot Test (for backend) are popular.



Integration Test Example with Testcontainers and Ktor

Suppose we have a Ktor application with PostgreSQL:

class UserRepository(private val database: Database) {    suspend fun findById(id: String): User? {        return database.query("SELECT * FROM users WHERE id = ?", id).singleOrNull { row ->            User(row.string("id"), row.boolean("is_premium"))        }    }}

Integration test with Testcontainers:

import io.kotest.core.spec.style.StringSpecimport io.kotest.matchers.shouldBeimport org.testcontainers.containers.PostgreSQLContainerimport org.testcontainers.junit.jupiter.Containerimport org.testcontainers.junit.jupiter.Testcontainers

@Testcontainersclass UserRepositoryIntegrationTest : StringSpec({

@Container val postgres = PostgreSQLContainer<Nothing>("postgres:15").apply { withDatabaseName("testdb") withUsername("test") withPassword("test") }

"should

Blogs

Book Recommendations