skip to content

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%

answer

  1. initialisers run at construction, before @BeforeEach
  2. lateinit unassigned ⇒ UninitializedPropertyAccessException
  3. build the subject AFTER init(this)
  4. @InjectMockKs sidesteps the ordering
  5. only literals / @SpyK values are initialiser-safe

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.

solid answer

~50 s

The failure is a lifecycle ordering problem, not a MockK bug. Kotlin runs property initialisers as part of constructing the test-class instance. JUnit constructs that instance **before** it calls any `@BeforeEach` method, which is where `MockKAnnotations.init(this)` lives. At construction time `repo` is an unassigned `lateinit`, so evaluating `OrderService(repo)` throws `UninitializedPropertyAccessException: lateinit property repo has not been initialized`. Three correct orderings: 1. Declare the subject `lateinit var service: OrderService` and assign it inside the setup method, **after** `MockKAnnotations.init(this)`. 2. Let MockK build it: `@InjectMockKs lateinit var service: OrderService`, since injection runs inside `init` after the mocks are created. 3. Make it lazy so it is first evaluated during the test, by which time `init` has run. The general rule: a property initialiser may only reference things that exist at construction time — literal values and `@SpyK` real objects, never `@MockK` properties.

code

kotlin · 23 lines
kotlin
// BROKEN: initialiser runs before MockKAnnotations.init
class BadTest {
    @MockK lateinit var repo: Repo
    val service = OrderService(repo)              // throws at construction
    @BeforeEach fun setUp() = MockKAnnotations.init(this)
}

// FIX 1: assign after init
class GoodTest {
    @MockK lateinit var repo: Repo
    lateinit var service: OrderService
    @BeforeEach fun setUp() {
        MockKAnnotations.init(this)
        service = OrderService(repo)
    }
}

// FIX 2: let MockK build and wire it
class GoodTest2 {
    @MockK lateinit var repo: Repo
    @InjectMockKs lateinit var service: OrderService
    @BeforeEach fun setUp() = MockKAnnotations.init(this)
}

go deeper

for a junior

Know that the subject must be created after MockKAnnotations.init(this), not in a property initialiser.

for a middle

Explain the three-step lifecycle — construction, setup, test — and why a lateinit mock is unassigned during step one.

for a senior

Give the fixes and their tradeoffs (explicit assignment vs @InjectMockKs vs lazy), and connect it to per-test instance freshness and double-initialisation hazards.

for a principal

Make it a convention: one initialisation mechanism per module, subjects constructed in setup or injected, and no property initialiser that depends on another property.

## The timeline Three things happen in a fixed order, and every version of this bug comes from assuming a different order: 1. **The test-class instance is constructed.** All property initialisers run here, top to bottom, exactly as in any Kotlin class. 2. **The setup method runs** (`@BeforeEach` or its equivalent). This is where `MockKAnnotations.init(this)` normally lives, and it is where the annotated `lateinit` properties finally receive their mocks. 3. **The test method runs.** So any expression written as a property initialiser executes in step 1, when every `@MockK lateinit var` is still unassigned. Kotlin's `lateinit` mechanism does not give you a null in that window — it throws `UninitializedPropertyAccessException` with the property's name, which is fortunately a very informative message once you know where to look. ## Why it is so easy to write `val service = OrderService(repo)` reads like a declaration of intent — "the subject is the service built from these collaborators" — and it compiles perfectly. Nothing in the type system marks `repo` as "not yet assigned"; that is exactly the tradeoff `lateinit` makes. The failure only appears at runtime, on every test in the class, usually as a stack trace pointing at the test class's constructor rather than at any test method. Candidates who have not hit it before often blame MockK's initialisation instead of their own evaluation order. ## The three fixes **Assign after init.** The most explicit form: ```kotlin @MockK lateinit var repo: Repo lateinit var service: OrderService @BeforeEach fun setUp() { MockKAnnotations.init(this) service = OrderService(repo) } ``` The wiring stays hand-written and compile-checked, and the ordering is visible in two adjacent lines. Note the order *within* the method matters too: constructing the subject before `init` reproduces the same exception. **Delegate to MockK.** `@InjectMockKs lateinit var service: OrderService` removes the ordering question entirely, because injection happens inside `init` after the doubles exist. The cost is that the wiring becomes reflective and no longer compile-checked. **Make it lazy.** `val service by lazy { OrderService(repo) }` defers evaluation to first use, which happens inside the test method, after setup. It works, but it hides an ordering constraint behind a delegate; use it when you have a reason, not by default. ## What *is* safe in an initialiser Property initialisers that do not touch annotated mocks are fine, and one of them is idiomatic: `@SpyK var clock = FixedClock(EPOCH)`. That expression only uses a literal, so construction-time evaluation is harmless — MockK later replaces the property's value with a spy around it. The rule generalises cleanly: an initialiser may reference only what exists at construction time. ## Per-test freshness Because JUnit's default lifecycle creates a **new** test-class instance for each test method, the whole sequence repeats per test: fresh instance, fresh mocks from `init`, fresh subject. That is what keeps stubs and recorded calls from leaking between tests, and it is another reason to build the subject in setup rather than trying to cache it — a cached subject would hold mocks from a previous test's initialisation. ## Related orderings that bite - **Stubbing before `init`.** Any `every { repo… }` written above the `init` call in the same setup method fails for the same reason. - **Two initialisation mechanisms.** If a JUnit 5 extension already initialises the annotations and you also call `MockKAnnotations.init(this)`, the mocks are created twice and the second set replaces the first — stubs recorded against the first set then appear to vanish. Pick one mechanism. - **Subject built from a spy.** Spies are also only wrapped during `init`, so a subject constructed in a property initialiser would capture the raw object rather than the spy, and verifications against the spy would see nothing. ## The sentence that answers the question "Property initialisers run at instance construction, before `@BeforeEach` and therefore before `MockKAnnotations.init(this)`, so the `lateinit` mock is still unassigned and Kotlin throws `UninitializedPropertyAccessException`. Build the subject after `init` — in the setup method, via `@InjectMockKs`, or lazily."

  • Inside the setup method, does it matter whether you construct the subject before or after MockKAnnotations.init(this)?
    Yes — constructing it first reproduces exactly the same failure, because the mocks are only assigned by `init`. The subject must be built after the `init` call. The same applies to any stubbing: `every { repo… }` written above the `init` line will throw on the unassigned `lateinit` property.
  • Why doesn't the @SpyK property initialiser suffer from the same problem?
    Because its initialiser references only a value that exists at construction time — typically a literal or a directly constructible object — rather than another annotated property. MockK later replaces the property's value with a spy wrapping it. The rule is about dependencies between properties, not about annotations: an initialiser is safe as long as it does not read a not-yet-assigned `lateinit`.

saying these in an interview costs you the question

  • Blaming MockK for UninitializedPropertyAccessException instead of the evaluation order.
  • Thinking @BeforeEach runs before the test class's property initialisers.
  • Constructing the subject above the MockKAnnotations.init call inside the setup method.
  • Assuming the mock is null rather than unassigned, and adding null checks.
  • Caching the subject across tests, which would pin it to a previous test's mocks.

context