Testing in Kotlin: unit, integration, e2e from A to Z

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

Testing in Kotlin: unit, integration, e2e from A to Z

\n\n

Modern development in Kotlin requires a systematic approach to testing. Whether you are writing a backend on Ktor or a mobile application for Android, high-quality test coverage is the key to project stability and predictability. In this article, we will cover three levels of testing: unit, integration, and e2e tests. You will learn which tools to use, how to write effective tests in Kotlin, and how to avoid common mistakes.

\n\n

1. Unit testing in Kotlin: the foundation

\n\n

Unit tests verify the work of individual functions, classes, or methods in isolation from external dependencies. In Kotlin, the JUnit 5 framework is most often used for this, combined with the mocking library MockK.

\n\n

Project setup

\n\n

Add dependencies to build.gradle.kts:

\n\n
dependencies {\n    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")\n    testImplementation("io.mockk:mockk:1.13.8")\n}\n
\n\n

Unit test example

\n\n

Suppose we have a simple service for calculating a discount:

\n\n
class DiscountService {\n    fun applyDiscount(price: Double, discountPercent: Int): Double {\n        require(discountPercent in 0..100) { "Discount percentage must be between 0 and 100" }\n        return price * (100 - discountPercent) / 100\n    }\n}\n
\n\n

Test for this service:

\n\n
import org.junit.jupiter.api.Assertions.*\nimport org.junit.jupiter.api.Test\nimport org.junit.jupiter.api.assertThrows\n\nclass DiscountServiceTest {\n\n    private val service = DiscountService()\n\n    @Test\n    fun `applyDiscount correctly calculates a 50 percent discount`() {\n        val result = service.applyDiscount(1000.0, 50)\n        assertEquals(500.0, result, 0.001)\n    }\n\n    @Test\n    fun `applyDiscount throws an exception for an invalid percentage`() {\n        assertThrows<IllegalArgumentException> {\n            service.applyDiscount(100.0, 150)\n        }\n    }\n}\n
\n\n

Tip: Use assertThrows to check exceptions and name tests with clear strings — this improves report readability.

\n\n

Mocking with MockK

\n\n

When your class depends on other components (e.g., a repository or API), use mocks:

\n\n
class OrderService(private val paymentGateway: PaymentGateway) {\n    fun processOrder(amount: Double): String {\n        if (paymentGateway.charge(amount)) {\n            return "Payment successful"\n        }\n        return "Payment error"\n    }\n}\n\n// Test\n@Test\nfun `processOrder returns success on successful payment`() {\n    val mockGateway = mockk<PaymentGateway>()\n    every { mockGateway.charge(any()) } returns true\n\n    val service = OrderService(mockGateway)\n    val result = service.processOrder(500.0)\n\n    assertEquals("Payment successful", result)\n    verify { mockGateway.charge(500.0) }\n}\n
\n\n

2. Integration testing: verifying component interaction

\n\n

Integration tests check how different parts of the system work together: database, external APIs, file system. Testcontainers is an excellent choice for Kotlin — a library for spinning up Docker containers during tests.

\n\n

Example with PostgreSQL database

\n\n

Add dependencies:

\n\n
testImplementation("org.testcontainers:testcontainers:1.19.3")\ntestImplementation("org.testcontainers:postgresql:1.19.3")\n
\n\n

Let's write a test for the DAO layer:

\n\n
class UserRepositoryIntegrationTest {\n\n    companion object {\n        private val postgres = PostgreSQLContainer<Nothing>("postgres:15").apply {\n            withDatabaseName("testdb")\n            withUsername("test")\n            withPassword("test")\n            start()\n                

Blogs

Book Recommendations