Introduction: Why tests without mocks are like coffee without caffeine?
\n\nHello, colleague! If you write in Kotlin and want your code not to break on every other commit, sooner or later you'll come to tests. And in tests — to mock objects and fixtures. Without them, unit tests turn into integration tests: you're not testing a single class, but a whole chain of dependencies. And if the database goes down or the API returns a 500 — the test will fail, even though your code might be perfect.
\n\nIn this article, we'll look at how to properly use Mock objects and fixtures in Kotlin tests so that tests are fast, reliable, and tell you the truth, not "everything is broken, fix it." Let's go!
\n\nWhat is a mock and why do you need it in Kotlin?
\n\nA mock object is an imitation of a real object. Instead of spinning up an entire server or database, we create a "stub" that behaves the way we tell it to. This allows you to isolate the code under test from the outside world.
\n\nBasic concepts
\n- \n
- Mock — an object whose method behavior we control. We can specify what it returns and verify whether it was called. \n
- Stub — a special case of a mock that only returns predefined values. It does not verify calls. \n
- Fixture — a set of pre-prepared data or states needed to run a test. For example, a test user or database settings. \n
In the Kotlin world, there are two main players: MockK (native to Kotlin) and Mockito (popular in Java). We will use MockK — it works better with coroutines, extension functions, and sealed classes.
\n\nProject setup: adding MockK and JUnit 5
\n\nFirst, let's add the dependencies to build.gradle.kts:
dependencies {\n testImplementation("io.mockk:mockk:1.13.8")\n testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")\n}\n\n\nNow we can write tests. Let's create a simple service and test it using mocks.
\n\nFirst example: mocking a repository
\n\nImagine we have a UserService that depends on UserRepository:
class UserService(private val repository: UserRepository) {\n fun getUserName(id: Long): String {\n val user = repository.findById(id)\n ?: throw IllegalArgumentException("User not found")\n return user.name\n }\n}\n\n\nNow let's write a test using a mock object for the repository:
\n\nimport io.mockk.every\nimport io.mockk.mockk\nimport io.mockk.verify\nimport org.junit.jupiter.api.Test\nimport kotlin.test.assertEquals\nimport kotlin.test.assertFailsWith\n\nclass UserServiceTest {\n\n private val repository = mockk<UserRepository>()\n private val service = UserService(repository)\n\n @Test\n fun `should return user name when user exists`() {\n // Arrange\n every { repository.findById(1L) } returns User(1L, "Alice")\n\n // Act\n val name = service.getUserName(1L)\n\n // Assert\n assertEquals("Alice", name)\n verify { repository.findById(1L) }\n }\n\n @Test\n fun `should throw exception when user not found`() {\n // Arrange\n every { repository.findById(any()) } returns null\n\n // Act & Assert\n assertFailsWith<IllegalArgumentException> {\n service.getUserName(99L)\n }\n }\n}\n\n\nNotice: every sets the mock's behavior, verify checks that the method was called. This is the foundation of mock objects and fixtures in Kotlin tests.
Fixtures: preparing data in advance
\n\nWhen there are many tests, duplicating data creation is bad practice. Fixtures come to the rescue. In Kotlin, they are conveniently