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?
answer
- doubles first, subject second
- uninitialised ⇒ constructor path
- pre-built ⇒ property path
- lookupType: BY_NAME / BY_TYPE / BOTH
- same type + rename = silent mis-wiring
basics
~20 sIt 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.
solid answer
~50 s`@InjectMockKs` marks the subject; `MockKAnnotations.init(this)` creates all the `@MockK`/`@RelaxedMockK`/`@SpyK` doubles first and then wires the subject from them. Two wiring paths exist, chosen by how you declared the property: - **Constructor** — the property is not initialised (e.g. `lateinit`), so MockK builds the instance, filling constructor parameters from the available doubles. - **Property** — you built the instance yourself (`@InjectMockKs var service = OrderService()`), so MockK assigns into the object's properties. Candidate matching considers **type and name**; `@InjectMockKs(lookupType = InjectionLookupType.BY_NAME)` or `BY_TYPE` narrows it. By default only mutable properties with no value are filled; `injectImmutable = true` also targets `val`s, and `overrideValues = true` replaces values already set. `@OverrideMockKs` is the shorthand for that overriding behaviour. The risk to name out loud: with two collaborators of the same type, matching falls to names, and a rename silently rewires the test instead of failing to compile.
code
kotlin · 17 linesclass OrderServiceTest {
@MockK lateinit var repo: OrderRepo
@RelaxedMockK lateinit var audit: Audit
// constructor path: MockK builds the subject from the mocks above
@InjectMockKs lateinit var service: OrderService
@BeforeEach fun setUp() = MockKAnnotations.init(this)
}
// property path: subject already exists, MockK writes into its properties
class LegacyTest {
@MockK lateinit var repo: OrderRepo
@InjectMockKs var subject = LegacyService()
@BeforeEach fun setUp() = MockKAnnotations.init(this)
}go deeper
Know that @InjectMockKs wires the subject from the mocks declared in the same test class, and that init(this) must run first.
Distinguish the constructor path from the property path by whether the subject is already initialised, and know that matching uses type and name.
Discuss lookupType, injectImmutable and overrideValues, and diagnose silent mis-wiring when two dependencies share a type or a property is renamed.
Decide where the reflective wiring is acceptable at all, and require explicit hand-wiring for subjects whose dependency graph is ambiguous or security-relevant.
## What the annotation is for In a test class annotated the MockK way, the collaborators are declared as `@MockK` properties. `@InjectMockKs` marks one more property — the *subject under test* — and asks MockK to connect the two. The whole thing happens inside `MockKAnnotations.init(this)`, in two phases: create all the doubles, then wire the subject. That ordering is what makes injection possible at all. ## Two wiring paths MockK supports injecting through the constructor and injecting into properties, and which one you get is decided by whether the subject already exists when `init` runs. **Constructor path.** Declare the subject uninitialised: ```kotlin @MockK lateinit var repo: OrderRepo @MockK lateinit var audit: Audit @InjectMockKs lateinit var service: OrderService ``` MockK has to create the instance, so it looks at the type's constructors and fills the parameters from the doubles it just created. This is the path most Kotlin code wants, because idiomatic Kotlin services take their dependencies as constructor parameters and expose them as `val`s. **Property path.** Construct the subject yourself: ```kotlin @InjectMockKs var service = OrderService() ``` Now there is an object already, so MockK assigns into its properties instead. This suits classes with settable dependencies — typically older or framework-driven designs. ## How candidates are matched For each thing to fill — a constructor parameter or a property — MockK looks for a suitable double among those created in the same `init`. Matching takes **both** the declared type and the name into account, and you can restrict it: ```kotlin @InjectMockKs(lookupType = InjectionLookupType.BY_NAME) lateinit var service: OrderService ``` `InjectionLookupType` offers `BY_NAME`, `BY_TYPE` and `BOTH`. Type matching is the intuitive one and works perfectly while every dependency has a distinct type. The moment two dependencies share a type — two `Clock`s, two `HttpClient`s, a primary and a fallback of the same interface — type alone cannot decide, and the name is what disambiguates. Forcing `BY_TYPE` in that situation, or forcing `BY_NAME` when your test property names differ from the subject's parameter names, is how tests end up wired to the wrong double. ## Mutability and overriding Two further switches matter on the property path: - **`injectImmutable`** — by default MockK targets mutable properties. Kotlin classes are full of `val`s, so `@InjectMockKs(injectImmutable = true)` exists to write those too, via reflection on the backing field. It works, but it deliberately violates the type system's immutability guarantee, which is a good reason to prefer the constructor path where possible. - **`overrideValues`** — by default a property that already holds a non-default value is left alone. Turning this on makes MockK replace what is there. `@OverrideMockKs` is the packaged version of "inject even into already-set and immutable properties", useful when the subject's own initialisers created real collaborators you want swapped out. ## Failure modes to name in an interview 1. **Silent mis-wiring.** Injection is reflective, so nothing is compile-checked. Rename a test property, or add a second dependency of an existing type, and the wiring can change without a single compiler error — the test still runs, just against the wrong double. Symptoms are bizarre: a verification fails on a mock that "was never called", because the subject holds a different one. 2. **A dependency MockK cannot satisfy.** Add a constructor parameter to the subject and forget to declare a matching double; the constructor path has nothing to supply for it. The failure appears at initialisation, well before the test's own assertions, and reads as an initialisation problem rather than a missing-mock problem. 3. **Order confusion.** Injection happens inside `init`, so anything the test does before `init` — notably property initialisers on the test class itself — cannot see the wired subject. 4. **Over-injection.** Because MockK fills whatever it can, a subject can silently receive mocks for dependencies the test never intended to fake, which weakens the test without anyone noticing. ## Practical guidance - Prefer the constructor path; it matches idiomatic Kotlin and avoids reflective writes into `val`s. - Keep dependency types distinct where you can, so type matching is unambiguous and names do not carry hidden significance. - When two dependencies do share a type, make the coupling explicit — either pin `lookupType` deliberately, or skip the annotation and construct the subject by hand. - Treat `injectImmutable` and `overrideValues` as escape hatches for designs you cannot change, not as defaults. ## The compressed answer "`init` creates the doubles, then wires the subject; uninitialised subject means constructor injection, pre-built subject means property injection; candidates are matched by type and name with `lookupType` to narrow it; `injectImmutable` reaches `val`s and `overrideValues`/`@OverrideMockKs` replace existing values — and because it is all reflective, same-type dependencies can be mis-wired with no compile error."
- Your subject has two constructor parameters of the same interface type — a primary and a fallback client. What does @InjectMockKs do?Type matching cannot distinguish them, so the outcome depends on name matching between your test properties and the subject's parameters. That is fragile: renaming either side silently changes the wiring with no compile error, and the test may verify against the wrong double. Either pin `lookupType` and keep names aligned deliberately, or drop the annotation for that test and construct the subject by hand so the wiring is explicit and compile-checked.
- When would you set injectImmutable = true, and what is the cost?You set it when the subject exposes its dependencies as `val` properties that you cannot convert to constructor parameters — typically legacy or framework-shaped classes. The cost is that MockK writes the backing field reflectively, bypassing the immutability the type declares; it also makes the test's wiring invisible in the subject's own API. Prefer the constructor path whenever the subject can accept its dependencies there.
- What does @OverrideMockKs add over plain @InjectMockKs?Plain injection leaves properties that already hold a value alone, so a subject that builds real collaborators in its own initialisers keeps them. `@OverrideMockKs` turns on the overriding behaviour so those existing values are replaced by the test's doubles, immutable properties included. It is the tool for swapping out self-constructed dependencies, and it should be used sparingly because it hides even more of the wiring.
saying these in an interview costs you the question
- Saying injection is by type only, and ignoring what happens with two same-type dependencies.
- Assuming MockK always constructs the subject, even when you assigned an instance yourself.
- Expecting a compile error when the wiring no longer matches — it is reflective and silent.
- Believing val properties are injected by default without injectImmutable.
- Thinking @InjectMockKs also creates the mocks; it only wires doubles that the same init created.