Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
155: Mocking with MockK In Depth
A few years ago, I was reviewing a PR for a teammate who had spent nearly an entire Tuesday fighting with a unit test for a payment processing flow. He had written a perfectly valid UserRepository mock, but his tests were crashing with a MockKException: no answer found for.... The confusing part? He had clearly defined the behavior using every { ... }. It took us twenty minutes of staring at the code to realize he was calling a suspend function inside a coroutine scope. In MockK, every is for synchronous calls; for suspending functions, you need coEvery. It's a tiny difference in naming, but it's one of those things that can make you want to throw your laptop out the window if you don't know it's happening.
Taming Coroutines with coEvery and coVerify
Since we're working in Kotlin, your services are likely riddled with suspend functions. When you're mocking these, you have to tell MockK to handle the continuation mechanism that powers coroutines. If you use every on a suspending function, you'll get a runtime crash because MockK doesn't know how to "suspend" the mock's response.
Instead, use coEvery to define the stub and coVerify to check if the call actually happened. It looks and feels exactly like the synchronous version, just with a "co" prefix. I usually recommend being explicit here—don't try to find a way around it with runBlocking inside the mock definition; just use the dedicated coroutine tools MockK provides.
val paymentGateway = mockk<PaymentGateway>()
// This would fail for a suspend function:
// every { paymentGateway.process(any()) } returns true
// This is the correct way:
coEvery { paymentGateway.process(any()) } returns true
// And verifying it:
coVerify { paymentGateway.process(paymentRequest) }
Capturing Arguments with Slots
Sometimes, simply verifying that a function was called isn't enough. You might need to inspect the exact object that was passed to a dependency to ensure your business logic transformed the data correctly before sending it off. This is where slot comes in. A slot acts like a container that "catches" the argument passed during the execution of the test.
I find slots far more readable than using complex match { ... } blocks inside the verify call. You capture the value first, and then you perform your assertions on the captured object using your favorite assertion library. It keeps the "mocking" phase and the "assertion" phase of your test cleanly separated.
val slot = slot<OrderRequest>()
val orderService = mockk<OrderService>()
coEvery { orderService.submitOrder(capture(slot)) } returns OrderResponse(success = true)
// ... call the code that triggers the order submission ...
assertEquals("Expected-Product-ID", slot.captured.productId)
assertEquals(2, slot.captured.quantity)
Balancing Strictness with Relaxed Mocks
By default, MockK is "strict." If your code calls a method on a mock that you haven't explicitly stubbed with every, MockK throws an exception. While this is great for catching unexpected side effects, it can become a nightmare when you're mocking a huge interface where your test only cares about one or two methods, but the class under test calls five other "utility" methods on that same mock.
You can use relaxed = true when creating the mock. A relaxed mock provides default values for all functions (empty strings, zero, or nulls for nullable types) without requiring you to stub every single call. Just be careful: if you relax everything, you might miss the fact that your code is calling a dependency it shouldn't be. I typically use relaxed mocks for "logger" or "analytics" dependencies—things that the system needs to function but aren't central to the logic being tested.
// No need to stub every single log call now
val logger = mockk<AppLogger>(relaxed = true)
val service = MyService(logger)
service.doWork()
// The test won't crash even if doWork() calls logger.info() five times
verify { logger.info(any()) }📋 Practical Task
Exercise: Testing the EmailNotificationDispatcher
You have a NotificationDispatcher class that depends on an EmailClient. The EmailClient has a suspending function sendEmail(email: String, body: String).
Your task is to write a test that ensures the NotificationDispatcher correctly formats the email body before sending it. The dispatcher should take a user's name and a message, and format the body as: "Hello [name], your message is: [message]".
Requirements:
- Use
mockkto create a mock ofEmailClient. - Use
coEveryto stub thesendEmailfunction to returntrue. - Use a
slotto capture thebodyargument passed tosendEmail. - Assert that the captured body matches the expected formatted string.
- Use
coVerifyto ensuresendEmailwas called exactly once.
// Provided classes for your test:
class EmailClient {
suspend fun sendEmail(email: String, body: String): Boolean = true
}
class NotificationDispatcher(private val client: EmailClient) {
suspend fun dispatchNotification(email: String, name: String, msg: String) {
val formattedBody = "Hello $name, your message is: $msg"
client.sendEmail(email, formattedBody)
}
}There are no comments for now.