Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I see this all the time when developers first move from older Java mocking frameworks to MockK: they assume that calling mockk<MyService>() creates a "dumb" object that will just return null or a default value when a method is called. They write their test, hit run, and are immediately greeted by a MockKException: no answer found for...

Here is the concrete reason why that's wrong. In MockK, mocks are strict by default. If you haven't explicitly told the mock how to behave for a specific call, it won't guess and it won't return a default; it will crash your test. This is actually a feature, not a bug. It forces you to be intentional about exactly what dependencies are being touched during your test execution.

The "It Just Works" Fallacy vs. Explicit Stubbing

Imagine we're building a PaymentProcessor that depends on an external BankApi. You might be tempted to just mock the API and call your process method:

val bankApi = mockk<BankApi>()
val processor = PaymentProcessor(bankApi)

// This will throw MockKException because we didn't define the behavior!
processor.chargeCustomer("cust_123", 50.0) 

To fix this, you have to use every { ... } returns .... This is where you define the "stub"—the predetermined response for a specific input. I always recommend being as specific as possible with your arguments to ensure your logic is actually passing the right data to the dependency.

every { bankApi.authorizePayment("cust_123", 50.0) } returns PaymentResponse(success = true)

// Now this works perfectly
processor.chargeCustomer("cust_123", 50.0)

If you truly don't care about the return value and just want the mock to stop complaining, you can use mockk<BankApi>(relaxed = true). But be careful. I've seen "relaxed" mocks hide bugs for weeks because a developer didn't realize their code was calling a dependency with the wrong arguments, and the relaxed mock just quietly returned a default value instead of failing.

Handling Coroutines with coEvery

Since we're working in Kotlin, you're almost certainly using suspend functions. A common mistake is trying to use every on a suspend function, which will result in a compiler error. For coroutines, you need the "co" prefix: coEvery.

It works exactly the same way, but it's designed to handle the continuation mechanism under the hood of Kotlin coroutines. If the function you're mocking is suspend, coEvery is your only option.

coEvery { bankApi.fetchAccountBalance("cust_123") } returns 1000.0

Verifying Side Effects with verify

Stubbing (telling the mock what to return) is only half the battle. Sometimes, a function doesn't return anything, but it's supposed to do something—like sending an email or logging an error. This is where verify comes in.

I like to think of verify as a way of asking the mock: "Did this actually happen?"

// Act
processor.chargeCustomer("cust_123", 50.0)

// Assert: Ensure the bank API was actually called exactly once
verify(exactly = 1) { bankApi.authorizePayment("cust_123", 50.0) }

You can also use verify(exactly = 0) to ensure a specific path was not taken, which is incredibly useful for testing error handling or validation logic where the expensive API call should be skipped.




📋 Practical Task

Exercise: Testing the UserNotificationDispatcher

You are tasked with testing a UserNotificationDispatcher class. This class has a dependency on a UserPreferencesRepository (to check if the user has notifications enabled) and an EmailClient (to actually send the message).

The Scenario: The dispatchNotification(userId: String, message: String) method should: 1. Call UserPreferencesRepository.isNotificationsEnabled(userId). 2. If true, call EmailClient.sendEmail(userId, message). 3. If false, it should NOT call the EmailClient.

Your Task: Write two tests using MockK:

  1. Success Case: Mock the repository to return true and verify that sendEmail was called exactly once with the correct arguments.
  2. Disabled Case: Mock the repository to return false and verify that sendEmail was never called.

Starter Code:

class UserNotificationDispatcher(
    private val prefsRepo: UserPreferencesRepository,
    private val emailClient: EmailClient
) {
    fun dispatchNotification(userId: String, message: String) {
        if (prefsRepo.isNotificationsEnabled(userId)) {
            emailClient.sendEmail(userId, message)
        }
    }
}

interface UserPreferencesRepository {
    fun isNotificationsEnabled(userId: String): Boolean
}

interface EmailClient {
    fun sendEmail(userId: String, message: String)
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.