| name | mocking-with-mockk |
| description | Use this skill to wire MockK (the Kotlin-first mocking framework) into a JVM unit-test suite, especially when coroutines, singleton/`object` mocking, or constructor mocking dominate. Covers `io.mockk:mockk-jvm:1.14.x` (and `mockk-android` + `mockk-agent` for instrumented), `every { } returns`, `coEvery` / `coVerify` (native suspend support — the big win over Mockito), `mockk(relaxed = true)` / `relaxUnitFun = true`, `slot<T>()` capture, `verifySequence` / `verifyOrder` / `confirmVerified`, `mockkStatic` / `mockkObject` / `mockkConstructor` with `unmockkAll()` cleanup, `@MockK` / `@RelaxedMockK` / `@SpyK` / `@InjectMockKs` annotations, and the Mockito-vs-MockK decision criteria. Use when the user mentions `coEvery`, `mockkObject`, `mockkStatic`, `slot`, `MockKException`, `every returns vs coEvery`, `relaxed = true`, or asks how to mock a Kotlin `object`/singleton. |
| license | Apache-2.0. See LICENSE for complete terms. |
| metadata | {"author":"Jaewoong Eum (skydoves)","keywords":["android-testing","mockk","kotlin-mocking","coroutines","coEvery","coVerify","mockkStatic","mockkObject","mockkConstructor","relaxed-mock"]} |
Mocking with MockK — The Kotlin-First Mocking Stack
MockK is the Kotlin-first alternative to Mockito. Its headline wins are native suspend support (coEvery / coVerify with no runBlocking dance), first-class singleton / object / static / constructor mocking, and Kotlin-native syntax (every { … } returns x). The trade-off: androidx itself does NOT use MockK (a grep -r "io.mockk" over the AOSP checkout returns zero hits — see ../mocking-with-mockito/SKILL.md for the dominant pattern). New Kotlin code outside Google often picks MockK; new Kotlin code inside Google or matching AOSP conventions picks Mockito. Both are valid.
When to use this skill
- The codebase is Kotlin-only and dominated by suspend functions / coroutines, and the user wants ergonomic suspend stubbing.
- The user needs to mock a Kotlin
object (singleton), companion object, top-level function (Kt-suffixed file), or every newly-constructed instance of a class.
- The user wants strict-by-default verification with the option to opt out per-mock via
relaxed = true / relaxUnitFun = true.
- The user wants
slot<T>() capture with slot.captured (cleaner than Mockito's ArgumentCaptor).
- The user is writing tests for
viewModelScope, Flow collectors, LaunchedEffect-style suspend collaborators.
When NOT to use this skill
- The codebase is mixed Java/Kotlin or already standardized on Mockito. Use
../mocking-with-mockito/SKILL.md. Do not migrate without a reason — both frameworks are valid.
- The user is matching AOSP / androidx conventions (which are Mockito-only). Use
../mocking-with-mockito/SKILL.md.
- Behaviour matters more than interactions, e.g. a
Repository with caching logic — write a fake instead. See ../../../fundamentals/doubles/picking-test-doubles/SKILL.md (Google explicitly prefers fakes per /test-doubles).
- The runner / Gradle matrix isn't set up yet — start with
../../runner/configuring-junit4-on-android/SKILL.md.
- The user is testing
Flow emissions over time — pair MockK with Turbine. See ../../coroutines/testing-flows-with-turbine/SKILL.md.
- The user needs
runTest semantics — see ../../coroutines/testing-coroutines-with-runtest/SKILL.md.
Prerequisites
- The base test wiring from
../../runner/configuring-junit4-on-android/SKILL.md is already in place.
- A
MainDispatcherRule is installed if any code-under-test touches Dispatchers.Main — see the runner skill.
- For instrumented MockK (Android runtime), the device must be API 21+ (MockK supports back to API 21 on dexmaker).
Workflow
dependencies {
testImplementation("io.mockk:mockk-jvm:1.14.0")
androidTestImplementation("io.mockk:mockk-android:1.14.0")
androidTestImplementation("io.mockk:mockk-agent:1.14.0")
}
mockk-android swaps the bytecode-generation backend so MockK runs inside Dalvik/ART where stock ByteBuddy doesn't work. Use mockk-jvm (NOT mockk-android) for src/test/ Robolectric tests — Robolectric runs on the JVM.
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.impl.annotations.SpyK
import io.mockk.impl.annotations.InjectMockKs
@RunWith(AndroidJUnit4::class)
class UserViewModelTest {
@get:Rule val mainDispatcherRule = MainDispatcherRule()
@MockK lateinit var repo: UserRepository
@RelaxedMockK lateinit var logger: Logger
@SpyK var realClock: Clock = SystemClock()
@InjectMockKs lateinit var subject: UserViewModel
@Before fun setUp() = MockKAnnotations.init(this, relaxUnitFun = true)
@After fun tearDown() = unmockkAll()
}
MockKAnnotations.init does not itself install global hooks (mockkStatic / mockkObject / mockkConstructor); but if the test class adds any of those, the @After unmockkAll() is needed to prevent leakage into sibling tests. JUnit5's MockKExtension does this automatically — JUnit4 does not.
Direct construction:
val car = mockk<Car>(
name = "carA",
relaxed = false,
relaxUnitFun = true,
moreInterfaces = arrayOf(Comparable::class),
)
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.verify
every { car.drive(Direction.NORTH) } returns Outcome.OK
every { car.brake() } throws IllegalStateException("frozen")
every { car.gear } returnsMany listOf(1, 2, 3)
every { car.compute(any()) } answers { firstArg<Int>() * 2 }
coEvery { repo.fetchUser(1) } returns User("Jane")
coEvery { repo.observeUser(1) } returns flowOf(User("Jane"))
verify { car.drive(Direction.NORTH) }
verify(exactly = 2) { car.drive(any()) }
verify(exactly = 0) { car.brake() }
verify(atLeast = 1, atMost = 3) { car.drive(any()) }
verifyAll { }
verifySequence { }
verifyOrder { }
coVerify(exactly = 1) { repo.fetchUser(1) }
confirmVerified(car)
confirmVerified is the safety net to catch silent extra interactions — pair it with verifyAll/verifySequence for a fully constrained test.
import io.mockk.slot
val slot = slot<User>()
every { repo.save(capture(slot)) } returns Unit
subject.register(User("Jane"))
assertEquals("Jane", slot.captured.name)
val users = mutableListOf<User>()
every { repo.save(capture(users)) } returns Unit
assertEquals(3, users.size)
val logger = mockk<Logger>(relaxed = true)
logger.info("anything")
val n = logger.lineCount
val analytics = mockk<Analytics>(relaxUnitFun = true)
analytics.track(Event("foo"))
analytics.session
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.mockkConstructor
import io.mockk.unmockkAll
@After fun tearDown() = unmockkAll()
@Test fun mocksObject() {
mockkObject(MySingleton)
every { MySingleton.flag } returns true
}
@Test fun mocksTopLevel() {
mockkStatic("com.example.UtilsKt")
every { hashSomething(any()) } returns "deadbeef"
}
@Test fun mocksJavaStatic() {
mockkStatic(System::class)
every { System.currentTimeMillis() } returns 0L
}
@Test fun mocksConstructor() {
mockkConstructor(OkHttpClient::class)
every { anyConstructed<OkHttpClient>().newCall(any()) } returns fakeCall
}
The JUnit5 MockKExtension auto-cleans, but JUnit4 tests must call unmockkAll() in @After.
Patterns
Pattern: WRONG vs RIGHT — suspend stubbing
every { mock.suspendFn() } returns x
coEvery { mock.suspendFn() } returns x
coVerify { mock.suspendFn() }
Pattern: WRONG vs RIGHT — leaking static / object mocks across tests
@Test fun firstTest() {
mockkStatic(System::class)
every { System.currentTimeMillis() } returns 0L
}
@Test fun secondTest() {
}
@After fun tearDown() = unmockkAll()
@Test fun firstTest() {
mockkStatic(System::class)
every { System.currentTimeMillis() } returns 0L
}
Pattern: WRONG vs RIGHT — picking a relaxation mode
val service = mockk<PaymentService>(relaxed = true)
service.charge(amount = 100, account = "acct-7")
val service = mockk<PaymentService>()
every { service.charge(any(), any()) } returns ChargeResult.OK
service.charge(amount = 100, account = "acct-7")
verify(exactly = 1) { service.charge(eq(100), eq("acct-7")) }
confirmVerified(service)
relaxed = true is appropriate for loggers / metrics where the test really doesn't care about return values. For domain services, prefer the default strict mode plus explicit every / verify.
Mandatory rules
- MUST use
coEvery { … } returns … (NOT every { … } returns …) when stubbing a suspend function. MUST use coVerify { … } (NOT verify { … }) when verifying a suspend call.
- MUST depend on
io.mockk:mockk-jvm for src/test/ and io.mockk:mockk-android + io.mockk:mockk-agent for src/androidTest/. Mixing the artifacts crashes the agent.
- MUST call
unmockkAll() in an @After method whenever a JUnit4 test uses mockkObject, mockkStatic, or mockkConstructor. Leakage across tests is the #1 MockK footgun.
- MUST prefer
relaxUnitFun = true over relaxed = true when in doubt. The middle ground hides far fewer bugs.
- MUST call
MockKAnnotations.init(this, relaxUnitFun = …) in @Before when using @MockK / @RelaxedMockK / @SpyK / @InjectMockKs. Without it, the lateinit fields never initialise.
- MUST prefer the JUnit5
MockKExtension over manual cleanup when the project is on JUnit 5 — it handles teardown of static / object mocks automatically.
- MUST match the rest of the codebase's mocking choice. Do not introduce MockK into a Mockito-standardized module without team alignment, and vice versa.
- MUST prefer fakes for behaviour-heavy collaborators (per developer.android.com/training/testing/fundamentals/test-doubles: "fakes ... are preferred"). Use MockK for verifying interactions, not for re-implementing behaviour.
- MUST NOT mock
inline functions or inline class (value classes) — MockK cannot intercept them. Wrap or refactor instead.
- MUST NOT rely on
mockkStatic for production-code time access (System.currentTimeMillis, Clock.system). Inject a Clock abstraction instead — production code is testable without bytecode rewriting.
- combine (or ) with for a fully constrained test that fails if any unexpected interaction occurs.
Verification
References