skip to content

When MockK builds an instance for `mockk<SomeClass>()`, are the class's constructor, `init` block and property initializers executed? Explain the consequences for the mock's internal state.

level: middleimportance: should knowfreq 28%

answer

  1. no <init>, no init block, no property initializers
  2. fields at JVM defaults — never read on a pure mock
  3. property access = intercepted getter, not the field
  4. side-effecting constructors never fire
  5. real state needed → construct a real object

basics

~20 s

No. MockK allocates the instance without invoking any constructor, so init blocks and property initializers never run and backing fields stay at JVM defaults. It does not matter for a pure mock, because every method is intercepted before real code could read a field.

solid answer

~50 s

`mockk<T>()` does not call `<init>`. MockK allocates the object directly (the Objenesis technique) and then relies on instrumentation to intercept every call. So: - constructor arguments are irrelevant — a class needing ten dependencies mocks as easily as an empty one; - constructors with side effects (opening connections, starting threads, reading config) do not fire; - `init` blocks and property initializers do not run, leaving backing fields `null` / `0` / `false`. That last point is harmless for a plain mock because no real method body executes: reading `mock.items` goes through the intercepted **getter**, which returns your stub or the strict-mock error, not the never-assigned field. It stops being harmless the moment real code runs on the instance — an answer that calls the original implementation can dereference a field a property initializer would normally have populated, and blow up with an NPE that looks nothing like a test bug. When you need real state, spy over a properly constructed object instead.

code

kotlin · 12 lines
kotlin
class Cache(private val source: Source) {
    val entries = mutableMapOf<String, String>()   // initializer never runs for a mock
    init { source.warmUp() }                       // never runs for a mock
    fun size(): Int = entries.size
}

val cache = mockk<Cache>()          // no Source argument needed at all

every { cache.entries } returns mutableMapOf("k" to "v")  // stubs the getter
every { cache.size() } returns 1                          // real body never executes

assertEquals(1, cache.size())

go deeper

for a junior

Know the headline: mockk() does not run the constructor, which is why you never pass constructor arguments to it.

for a middle

Explain that init blocks and property initializers are skipped, fields sit at JVM defaults, and that property access is an intercepted getter call rather than a field read.

for a senior

Add the failure mode — real code executing on a constructor-less instance can NPE on unpopulated fields — and the rule that real state means a real, constructed object.

for a principal

Use it when arguing about test-design boundaries: a pure mock deliberately has no invariants, so tests that need construction-established state belong on real objects or integration-level tests, not on mocks.

## The mechanism Creating a mock has two halves: getting an **object**, and intercepting its **calls**. MockK's agent handles interception by instrumenting the class's bytecode. For the object itself, MockK does not use a constructor at all — it allocates an instance of the class directly, the technique popularised by the Objenesis library, which asks the JVM for a bare instance without running `<init>`. That is why `mockk<T>()` needs no arguments regardless of what `T`'s constructor demands, and why MockK does not care whether the class has a no-arg constructor. ## What does not run Everything that is part of construction is skipped: - the primary and any secondary **constructor bodies**; - **`init { }` blocks**; - **property initializers** (`val items = mutableListOf<String>()`); - superclass constructors. So after `val cache = mockk<Cache>()`, the object's backing fields hold JVM defaults: object references are `null`, numeric fields `0`, booleans `false`. ## Why that is usually invisible Because **you never execute real method bodies on a mock**. Instrumentation puts the dispatcher hook at the top of each method; the hook returns a stubbed answer (or throws "no answer found for: …" on a strict mock), and the original code below it never runs. Field reads therefore do not happen. A point that trips people: in Kotlin, a property access is a *method call* to a getter. `cache.entries` compiles to `getEntries()`, which is intercepted like any other member. You are not reading the null backing field — you are calling an intercepted getter that returns whatever you stubbed. That is why `every { cache.entries } returns mutableMapOf()` works perfectly on an object whose field was never assigned. ## Where it bites The uninitialised state becomes real the moment **original code actually executes on that instance**: - an answer that delegates to the original implementation; - a partially real object where some methods pass through. Then the real body runs against fields that no initializer ever populated, and you get a `NullPointerException` deep inside production code — a confusing failure, because the exception points at code that is correct and would never fail in production. The general rule: **if you want a real object with real state, build a real object** (and spy on it if you need to observe or override a few members); use a pure mock when you intend that *no* real logic runs. A second, subtler consequence: any invariant your constructor establishes does not exist on a mock. If a class's constructor validates arguments or derives a field, the mock is a shape that could never occur in production. That is normally fine — the mock is a stand-in, not the thing — but it is worth naming when someone argues "the mock proves the class works". It proves nothing about the class; it only stands in for it. ## Practical upside This design is a large part of why MockK feels frictionless on real codebases: - classes with expensive or side-effecting constructors (HTTP clients, connection pools, framework beans) mock instantly; - you never assemble a constructor argument list just to obtain a stand-in; - constructor changes in production code do not ripple into every test that mocks the type — a real maintenance saving compared with hand-written fakes. ## Interview framing The expected answer: *no constructor, no `init`, no property initializers — the instance is allocated directly, and interception means real bodies never run, so default fields are invisible. Property access goes through the intercepted getter, not the field. It only matters when real code executes on the instance, which is a reason to prefer a real, constructed object when you need real state.*

  • If the backing field of `entries` is null, why does `every { cache.entries } returns …` work?
    In Kotlin a property access compiles to a getter call, and the getter is an instrumented method like any other. MockK intercepts the getter and returns your stubbed value, so the never-assigned backing field is simply never read. The same is true for a property setter, which is an intercepted method too.
  • When does the skipped constructor actually cause a failure?
    When real code runs on that instance — for example an answer that calls the original implementation. The original body then reads fields a property initializer would normally have populated and throws a NullPointerException inside otherwise-correct production code. If a test needs genuine internal state, construct a real object and spy on it rather than asking a constructor-less mock to run real logic.

saying these in an interview costs you the question

  • "mockk() needs a no-arg constructor" — it needs no constructor at all.
  • "My init block ran, so the mock is initialised" — construction is entirely skipped.
  • "Reading a property on a mock returns null because the field is null" — the getter is intercepted; the field is never read.
  • "Mocking a class proves the class's constructor invariants hold" — a mock has none of the class's real state or invariants.
  • "Just make the mock call the original method to get real state" — the real body may dereference fields no initializer populated.

context