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?
answer
- annotate the parameter, extension resolves it
- fresh mock per resolution — property ≠ parameter
- relaxed / relaxUnitFun attributes honoured
- name: annotation name → parameter name (-java-parameters) → none
- @AdditionalInterface for extra interfaces
basics
~20 sAnnotate 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.
solid answer
~50 sMockKExtension is a parameter resolver: any parameter of a method JUnit resolves parameters for — test functions, lifecycle methods, the test constructor — is supplied by MockK when it carries `@MockK` or `@RelaxedMockK`. ```kotlin @Test fun `sends receipt`(@RelaxedMockK mailer: Mailer, @MockK(relaxUnitFun = true) repo: Repo) { … } ``` Each resolution builds a **new** mock, so two parameters of the same type are two distinct mocks, and a parameter mock is never the same object as a same-typed `@MockK` property. `@MockK`'s `relaxed` and `relaxUnitFun` attributes are honoured; `@RelaxedMockK` is always relaxed. The mock's name is the annotation's `name` if set, otherwise the parameter's own name when the code is compiled with `-java-parameters`, else unnamed — names appear in MockK's failure messages. `@AdditionalInterface` on the parameter adds an extra interface to the mock. Use parameters for collaborators only one or two tests need; keep shared ones as properties.
code
kotlin · 19 lines@ExtendWith(MockKExtension::class)
class ReceiptTest {
@MockK
lateinit var repo: OrderRepository // shared by every test
@Test
fun `mails a receipt once`(
@RelaxedMockK mailer: Mailer,
@MockK(name = "clock") clock: Clock,
) {
every { clock.now() } returns Instant.EPOCH
every { repo.find(1L) } returns Order(1L)
Receipts(repo, mailer, clock).send(1L)
verify(exactly = 1) { mailer.send(any()) }
}
}go deeper
Know that annotating the parameter with @MockK or @RelaxedMockK is enough, and that the extension must be on the class.
Explain per-resolution creation (parameter mocks are distinct objects), which annotation attributes carry over, and when a parameter beats a property.
Add the diagnostics: wrong-instance stubbing when property and parameter overlap, mock naming and the -java-parameters dependency for readable failures, strict-vs-relaxed choice per collaborator.
Set the convention — shared collaborators as properties, one-off doubles as parameters — and require named mocks so CI failure output identifies the double without opening the test.
## The mechanism `MockKExtension` implements JUnit's parameter-resolution contract. For every parameter JUnit asks about, the extension looks for a MockK annotation on that parameter; if it finds one, it says it supports the parameter and then builds the value. That is the whole trick — there is no separate registration, no naming convention, nothing implicit. **A parameter with no MockK annotation is not touched**, which is why an unannotated parameter fails with JUnit's ordinary "no resolver" error rather than silently receiving a mock. Where it applies: any method whose parameters JUnit resolves — `@Test` functions, lifecycle methods, and the test class constructor. The constructor case is its own topic because ordering matters there. ## What gets created - `@RelaxedMockK param: T` → a relaxed mock: unstubbed calls return default values instead of throwing. - `@MockK param: T` → a strict mock by default; the annotation's `relaxed` and `relaxUnitFun` attributes are read and passed through, so `@MockK(relaxUnitFun = true)` gives you a mock that tolerates unstubbed `Unit`-returning calls but still fails on unstubbed value-returning ones. - `@AdditionalInterface(type = …)` on the same parameter adds a further interface to the created mock, for the case where production code casts the collaborator. Resolution happens **per invocation**. Declare two parameters of the same type and you get two independent mocks. Declare a `@MockK` property of type `Repo` *and* a `@MockK` parameter of type `Repo` and you have two different objects — a very common source of "I stubbed it but the call was never stubbed" confusion, because the class under test was wired with one of them and the stub went to the other. ## Naming, and why it is worth setting Every MockK mock has a name that shows up in error and verification messages ("no answer found for ...", the list of recorded calls). For a parameter mock the name is chosen in this order: the annotation's `name` attribute if you set one; otherwise the parameter's own source name — but only if parameter names survived compilation, which for Kotlin means the `-java-parameters` compiler flag; otherwise the mock is unnamed and messages fall back to a generic identifier. On a project that has not enabled that flag, setting `@MockK(name = "repo")` is cheap and makes failures readable. ## Why choose a parameter over a property **Locality.** A collaborator that exists for one test does not belong in class scope where every other test's reader has to decide whether it matters. A signature like `fun retries on timeout(@MockK clock: Clock)` says exactly which doubles the test involves. **No lateinit.** Property mocks are typically `lateinit var`, which is mutable class state that JUnit and the extension fill in; a parameter is a `val` with an obvious origin. **Cost.** Shared collaborators become repetitive noise if you thread them through five signatures, and shared stubbing in `@BeforeEach` cannot see a parameter that does not exist yet. So: shared setup → properties; one-off doubles → parameters. Mixing both in one class is normal and fine. ## Relationship to the rest of the extension Parameter resolution is independent of the property initialisation pass: a class with only parameter mocks still needs `@ExtendWith(MockKExtension::class)`, and it still gets the extension's end-of-test `unmockkAll()`/`clearAllMocks()`. Note also that a parameter mock is created *before* the test body runs, so `every { }` inside the test is the first thing that touches it — there is no pre-stubbing hook attached to the parameter itself. ## Failure modes to recognise - **Unannotated parameter** → resolution error from the test framework, not from MockK. Add the annotation. - **Extension not registered** → same error; the parameter resolver only exists when the extension is on the class (or auto-detected). - **Stub applied to the wrong instance** → you stubbed the property while the subject holds the parameter mock (or vice versa). Wire the subject inside the test from exactly the doubles you intend. - **Strict-mock surprises** → `@MockK` on a parameter is strict; unstubbed calls throw. That is usually what you want, but if the collaborator is a fire-and-forget sink, `@RelaxedMockK` is the honest choice.
- A test declares a @MockK property of type Repo and a @MockK parameter of the same type. Are they the same mock?No. Property mocks are created during test-instance post-processing and parameter mocks during parameter resolution, and each resolution builds a new mock. You end up with two independent objects that record calls separately. If the class under test is constructed with one and you stub the other, the stub simply never applies — pick one source per collaborator.
- Why might the mock in a failure message show a generic name instead of the parameter's name?Because the parameter's source name did not survive compilation. MockK falls back to the parameter name only when it is present in the bytecode, which requires the -java-parameters compiler flag for Kotlin. Without it the mock is unnamed unless you set @MockK(name = "...") explicitly, which is the reliable way to keep messages readable.
saying these in an interview costs you the question
- Assuming any parameter of the right type is auto-mocked — resolution keys off the annotation, not the type.
- Treating a same-typed @MockK property and @MockK parameter as one object.
- Thinking parameter injection works without registering MockKExtension on the class.
- Believing @MockK on a parameter produces a relaxed mock; it is strict unless relaxed/relaxUnitFun say otherwise.
- Putting every collaborator in the signature until the test declaration is unreadable.