skip to content

Coroutines, Annotations and JUnit 5

Mocking suspend functions and wiring mocks into test classes with annotations and the JUnit 5 extension. Interviewers ask this because real Kotlin services are coroutine-heavy and mock setup boilerplate is where suites rot.

on this pageshow

explore

questions

14

In a test class that declares MockK's @MockK and @SpyK properties, what does MockKAnnotations.init(this) actually do, and what breaks if you forget to call it?

level: middleimportance: must knowfreq 55%

answer

  1. annotations are inert markers
  2. init(this) reflects + assigns
  3. mocks created before @InjectMockKs wiring
  4. forgot init ⇒ UninitializedPropertyAccessException
  5. init(this, relaxUnitFun = true) is class-wide

basics

~20 s

It reflects over the given instance, creates a mock or spy for every MockK-annotated property and assigns it. Without it those lateinit properties are never set, so the first use throws UninitializedPropertyAccessException — the annotations do nothing on their own.

solid answer

~50 s

MockK's annotations are inert markers. `MockKAnnotations.init(this)` is the step that scans the passed object's properties, creates the corresponding double for each — `@MockK` and `@RelaxedMockK` produce mocks, `@SpyK` wraps the property's existing value in a spy, `@InjectMockKs` gets the subject wired — and assigns them back onto the properties. Call it once per test instance, from the `@BeforeEach`/`setUp` method, before anything touches the mocks. Skip it and the `lateinit var` properties are simply never assigned, so the first access throws `UninitializedPropertyAccessException: lateinit property repo has not been initialized` — a failure people often misread as a MockK bug. `init` also takes class-wide flags: `MockKAnnotations.init(this, relaxUnitFun = true)` (or `relaxed = true`) applies that behaviour to every annotated mock in the class, saving per-annotation configuration. Because JUnit creates a fresh test instance per test method by default, `init` runs again for each test and you get fresh mocks.

code

kotlin · 14 lines
kotlin
class OrderServiceTest {

    @MockK lateinit var repo: OrderRepo
    @RelaxedMockK lateinit var audit: Audit
    @SpyK var clock = FixedClock(EPOCH)

    lateinit var service: OrderService

    @BeforeEach
    fun setUp() {
        MockKAnnotations.init(this, relaxUnitFun = true)
        service = OrderService(repo, audit, clock)   // built AFTER init
    }
}

go deeper

for a junior

Know that the annotations need MockKAnnotations.init(this) in a setup method, and that forgetting it causes UninitializedPropertyAccessException.

for a middle

Describe what init does per annotation, the mocks-then-injection ordering, and the class-wide relaxed/relaxUnitFun parameters.

for a senior

Tie it to the test lifecycle: per-method instances give fresh mocks, shared instances need explicit clearing, and the subject must be built after init.

for a principal

Make it a house style — one initialisation mechanism per module, never both an extension and a manual init, and an explicit rule about where the subject is constructed.

## Annotations do nothing by themselves `@MockK`, `@RelaxedMockK`, `@SpyK` and `@InjectMockKs` are plain Kotlin annotations. Nothing in the JVM acts on them; no compiler plugin rewrites your class. They are metadata waiting for someone to read it. `MockKAnnotations.init(...)` is that reader: it takes one or more objects, reflects over their properties, finds the annotated ones, constructs the appropriate double for each and assigns it to the property. So the mental model is a two-step: *declare* with an annotation, *materialise* with `init`. Miss the second step and you have a test class full of declarations and no objects. ## What init creates for each annotation - `@MockK lateinit var repo: Repo` → `mockk<Repo>()` assigned to `repo`. Optional parameters on the annotation (`relaxed`, `relaxUnitFun`, and a name) are passed through to the creation. - `@RelaxedMockK lateinit var repo: Repo` → the same as `@MockK(relaxed = true)`. - `@SpyK var clock = FixedClock()` → a spy wrapping the value the property already holds; that is why a `@SpyK` property must be initialised by you and is normally a plain `var`, not `lateinit`. - `@InjectMockKs` → handled after the mocks exist, so the subject can be wired from them. That last point is an ordering guarantee worth remembering: within a single `init` call, the doubles are created first and injection into the subject happens afterwards. Otherwise there would be nothing to inject. ## The failure mode when you forget Because `@MockK` properties are conventionally `lateinit var`, forgetting `init` does not produce a null — Kotlin's `lateinit` throws on read: ``` kotlin.UninitializedPropertyAccessException: lateinit property repo has not been initialized ``` The stack trace points at your test code, not at MockK, so the cause is easy to misdiagnose. Any time you see that exception in a MockK test, the first two hypotheses are: (a) `init` was never called, or (b) it was called *after* something already used the property. ## Where to call it The conventional place is a setup method that runs before each test: ```kotlin @BeforeEach fun setUp() = MockKAnnotations.init(this) ``` JUnit's default lifecycle creates a new test-class instance per test method, so `init` runs per test and each test gets fresh, unstubbed doubles — no cross-test bleed from stubs or recorded calls. If a class-level lifecycle is used instead, the instance and its mocks are shared and you must clear them between tests yourself. `init` accepts several objects (`MockKAnnotations.init(this, other)`), which is occasionally useful when annotated properties live on a shared base or helper object. ## Class-wide flags `init` takes named parameters that apply to every annotated mock it creates in that call — notably `relaxUnitFun = true` and `relaxed = true`. `MockKAnnotations.init(this, relaxUnitFun = true)` is the compact way to say "none of these mocks should force me to stub `Unit` members", instead of repeating a parameter on each annotation. Per-annotation settings remain available when only one mock needs different treatment. ## The alternative to calling it yourself On JUnit 5, MockK ships an extension that performs the initialisation for you when the test class is annotated with it; then the explicit `init` call is redundant and should be removed rather than duplicated. That integration is a topic of its own; the thing to hold onto here is simply that *something* must run the initialisation, and if no extension is doing it, your setup method must. ## Interview-shaped summary Say it as a chain: annotations are inert → `init(this)` reflects and assigns → mocks first, subject injection second → forgetting it surfaces as `UninitializedPropertyAccessException` on first use → JUnit's per-method instance means it re-runs and mocks are fresh → `relaxUnitFun`/`relaxed` on the `init` call configure the whole class at once.

  • Do you need to clear the mocks between tests when you call MockKAnnotations.init in a per-test setup method?
    Usually not: JUnit's default lifecycle builds a fresh test-class instance for every test method, so `init` runs again and creates brand-new doubles with no stubs and no recorded calls. Clearing becomes necessary when the instance is shared — a class-level lifecycle, mocks held in a companion object, or doubles created once in a `@BeforeAll`-style hook.
  • What is the difference between passing relaxUnitFun = true to MockKAnnotations.init and putting it on a single @MockK annotation?
    The `init` parameter applies to every annotated mock created by that call, which is the compact option when the whole class wants the same policy. The annotation parameter scopes it to one property, which is better when only one collaborator has many Unit-returning members and you want the others to stay strict so unexpected calls still fail.

saying these in an interview costs you the question

  • Thinking the annotations create mocks on their own without any init step.
  • Reading UninitializedPropertyAccessException as a MockK bug rather than a missing or late init call.
  • Calling init after already touching the mocks in the same setup method.
  • Believing init must be called once per class rather than per test instance.
  • Assuming @SpyK works on a lateinit property with no real value to wrap.

context

open as a page

In a JUnit 5 test class annotated with @ExtendWith(MockKExtension::class), what work does MockK's extension actually perform, and at which points of the test lifecycle does it perform it?

level: middleimportance: must knowfreq 45%

basics

~20 s

It initialises annotated mock properties on each new test instance, resolves MockK-annotated test parameters, and afterwards runs unmockkAll() plus clearAllMocks() — after every test method, or after the whole class when the instance lifecycle is PER_CLASS.

open as a page

A MockK test declares `@MockK lateinit var repo: Repo` and builds the subject in a property initialiser: `val service = OrderService(repo)`. Why does it blow up, and how do you order the setup correctly?

level: seniorimportance: must knowfreq 38%

basics

~20 s

Property initialisers run when the test instance is constructed, before any setup method and before MockKAnnotations.init(this) assigns the mocks. So repo is still unset and the initialiser throws UninitializedPropertyAccessException. Build the subject after init, or let @InjectMockKs do it.

open as a page

How does MockK's @InjectMockKs decide what to put into the subject under test — does it use the constructor or the properties, and does it match by name or by type?

level: seniorimportance: must knowfreq 42%

basics

~20 s

It draws from the doubles created in the same test class. If the annotated property has no instance yet, MockK constructs the subject through a constructor it can satisfy; if you assigned an instance, it injects into its properties. Matching considers type and name, tunable with lookupType.

open as a page

Compare MockK's @MockK, @RelaxedMockK and @SpyK: what object does each property end up holding, and how must each property be declared?

level: middleimportance: should knowfreq 40%

basics

~20 s

@MockK and @RelaxedMockK both put a mock in the property and go on lateinit var of the mocked type; @RelaxedMockK equals @MockK(relaxed = true). @SpyK wraps a real value you supply, so its property must be an initialised var, not lateinit.

open as a page

With MockK's JUnit 5 extension registered, how do you have a mock handed to a single test function as a parameter rather than declared as a class property, and what controls that mock's relaxation and name?

level: middleimportance: should knowfreq 33%

basics

~20 s

Annotate the test function's parameter with @MockK or @RelaxedMockK; MockKExtension resolves it and creates a fresh mock per invocation. @MockK's relaxed and relaxUnitFun attributes apply; the name comes from the annotation's name attribute, otherwise from the parameter name.

open as a page

A collaborator exposes a suspend function that returns Unit. How do you stub it in MockK without inventing a return value, and what exactly does MockK's coJustRun do?

level: middleimportance: should knowfreq 45%

basics

~20 s

Write coJustRun { audit.record(any()) }. It is MockK's suspend-capable shorthand for coEvery { audit.record(any()) } just Runs: the call is recorded, does nothing and returns Unit. Creating the mock with relaxUnitFun = true stubs every Unit function instead.

open as a page

A single MockK mock stands in for an interface that has both suspend and ordinary functions. How do you stub and verify each kind, and can one verification block cover calls of both kinds?

level: middleimportance: should knowfreq 48%

basics

~20 s

One mock handles both: use every/verify for ordinary members and coEvery/coVerify for suspend ones. The co-variants also accept ordinary calls, so coVerifySequence can cover a mixed run; the plain verify block cannot contain a suspend call at all.

open as a page

You want MockK's JUnit 5 extension to supply collaborator mocks through your test class's constructor and to build the class under test there with @InjectMockKs. What does the extension require about the order of those constructor parameters, and what happens if you get it wrong?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Declare every collaborator mock before the parameter that consumes them. The extension caches resolved mocks by type as JUnit fills constructor parameters left to right, and builds the @InjectMockKs parameter from that cache; a dependency declared later is missing, and MockK throws telling you to reorder.

open as a page

What do MockK's @MockKExtension.ConfirmVerification and @MockKExtension.CheckUnnecessaryStub class annotations make happen, when do they run, and what is the cost of switching them on across a whole test suite?

level: seniorimportance: should knowfreq 25%

basics

~20 s

They make MockKExtension run a global confirmVerified() and checkUnnecessaryStub() at the end of the test, failing it when a recorded call went unverified or a stub was never used. Both are class-level, and both can be turned on suite-wide by a JUnit configuration parameter.

open as a page

MockK lets you finish a stub for a suspend function with either `answers { }` or `coAnswers { }`. What is the concrete difference between the two blocks, and in whose coroutine does a `coAnswers` body execute?

level: seniorimportance: should knowfreq 42%

basics

~20 s

coAnswers takes a suspending lambda, so it may call delay or other suspend functions; the plain answers block is non-suspending and cannot. The coAnswers body runs inline in the caller's coroutine, so the caller's dispatcher and cancellation apply to it.

open as a page

You need a test to prove that your service abandons a slow dependency under withTimeout. How do you make a MockK-stubbed suspend function actually suspend, and what must you avoid?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Stub it with a suspending answer: coEvery { client.fetch() } coAnswers { delay(10_000); Response }. The delay runs in the caller's coroutine, so it is a real cancellable suspension point. Never fake latency with Thread.sleep in a plain answers block.

open as a page

When would you deliberately not use MockK's @InjectMockKs and wire the subject under test by hand instead?

level: principalimportance: nice to knowfreq 24%

basics

~20 s

When the wiring needs to be obvious and compile-checked: ambiguous same-type dependencies, subjects whose constructor changes often, or tests where readers must see exactly which collaborator is faked. Hand wiring fails at compile time; reflective injection fails silently or not at all.

open as a page

A Kotlin team enables parallel JUnit 5 execution on a suite that uses MockK's MockKExtension, and mocks start losing their stubs mid-test. What does the extension do that breaks under parallel execution, and how would you decide what to do about it?

level: principalimportance: nice to knowfreq 16%

basics

~20 s

Its end-of-test cleanup calls process-global unmockkAll() and clearAllMocks(), which wipe every mock in the JVM — including ones a concurrently running test is still using. MockK's answer is @MockKExtension.RequireParallelTesting (or KeepMocks) to suppress that cleanup, after which isolation is yours.

open as a page