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?
answer
- annotations are inert markers
- init(this) reflects + assigns
- mocks created before @InjectMockKs wiring
- forgot init ⇒ UninitializedPropertyAccessException
- init(this, relaxUnitFun = true) is class-wide
basics
~20 sIt 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 sMockK'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 linesclass 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
Know that the annotations need MockKAnnotations.init(this) in a setup method, and that forgetting it causes UninitializedPropertyAccessException.
Describe what init does per annotation, the mocks-then-injection ordering, and the class-wide relaxed/relaxUnitFun parameters.
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.
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.