skip to content

MockK exposes an infix DSL written as `mock getProperty "speed"` and `mock setProperty "speed" value 33`. What problem does it solve, what does it require, and what does it cost you?

level: seniorimportance: nice to knowfreq 22%

answer

  1. private property → no expression to record
  2. mock getProperty "name"
  3. setProperty "name" value matcher
  4. spyk(obj, recordPrivateCalls = true)
  5. string name = rename-unsafe, untyped

basics

~20 s

It reaches properties the test cannot name in code — private ones — by string, reflectively. On a spy you need spyk(obj, recordPrivateCalls = true) for private access to be recorded. Cost: no compile-time safety, rename-unsafe, untyped results.

solid answer

~50 s

Normal property stubbing (`every { mock.prop }`) needs the property to be *visible* from the test source. For a `private` property there is no expression you can write, so MockK provides a reflective, string-named DSL inside recording blocks: ```kotlin every { mock getProperty "speed" } returns 33 every { mock setProperty "speed" value less(50) } just Runs verify { mock getProperty "speed" } ``` Matchers work in the `value` position, so setter constraints stay expressive. Two requirements matter. First, for a spy over a real object, private accesses are only recorded if you create it with `spyk(obj, recordPrivateCalls = true)` — otherwise the internal call is invisible to verification. Second, the property name is a **string**: renaming the property leaves the test compiling and silently wrong, and the result is untyped so you cast. Use it for legacy or third-party classes you cannot change. Where you own the code, widening the property to `internal` and testing through the public surface is the better fix.

code

kotlin · 11 lines
kotlin
class Car {
    private var speed: Int = 0
    fun accelerate() { speed += 10 }
    fun isFast() = speed > 50
}

val car = spyk(Car(), recordPrivateCalls = true)
every { car getProperty "speed" } returns 90

assertTrue(car.isFast())
verify { car getProperty "speed" }

go deeper

for a junior

Know that it exists for properties the test cannot name, and that the normal every { mock.prop } form is the one you use day to day.

for a middle

Show the syntax for get and set, note that matchers work in the value position, and mention the string name is not refactor-safe.

for a senior

Add the recordPrivateCalls = true requirement on spies and argue the encapsulation cost — brittle coupling to storage rather than behaviour.

for a principal

Set the policy: reflective access is a scaffold for legacy or third-party code with an exit plan, not a pattern the team reaches for on code it owns.

## The gap it fills MockK's normal property stubbing is *expression-based*: `every { car.speed } returns 33` works because the test can write `car.speed`. Kotlin visibility decides whether that expression compiles. `private` members are invisible outside their class, and `internal` members are invisible outside the module, so for those there is simply no expression to record. MockK's answer is a reflective escape hatch that identifies the member by name. ## The DSL Inside `every { }`, `coEvery { }` or `verify { }` blocks: ```kotlin every { mock getProperty "speed" } returns 33 every { mock setProperty "acceleration" value less(5) } just Runs verify { mock getProperty "speed" } verify { mock setProperty "acceleration" value less(5) } ``` `getProperty` records a getter invocation by name; `setProperty ... value ...` records a setter invocation, and the `value` position accepts ordinary argument matchers, so you can constrain which assignments you are stubbing or verifying. It belongs to the same family as MockK's dynamic method calls (`invoke "name" withArguments listOf(...)`, `invokeNoArgs "name"`), which exist for the same visibility reason. ## The `recordPrivateCalls` requirement The DSL is most often used against a **spy** over a real object, because that is where private state actually lives. By default a spy does not record calls the object makes to its own private members — recording every internal hop would be noisy and would change how much of the object's behaviour is observable. Passing `recordPrivateCalls = true` at construction turns that recording on: ```kotlin val car = spyk(Car(), recordPrivateCalls = true) every { car getProperty "speed" } returns 90 ``` Without the flag, a stub or verification on a private property has nothing to bind to and the test fails in a confusing way — a *no answer found* or a verification that never sees the call. On a pure `mockk<T>()` the flag is not the issue; the issue is only whether the member exists on the mocked type. ## What it costs 1. **No compile-time checking.** `"speed"` is a string literal. Rename the property in the production class and the test still compiles; it fails at runtime, or worse, silently stops matching what you think it matches. 2. **Refactoring tools do not follow it.** An IDE rename will not update the literal unless it happens to do string search, so the test drifts away from the code. 3. **Untyped results.** The recorded expression is not statically typed to the property's type, so you end up asserting on or casting an `Any?`-shaped value, losing the compiler's help. 4. **It cements encapsulation breaches.** A test that asserts on a private field is coupled to *how* the class stores things, not what it does. Any internal refactor that keeps behaviour identical can still break the test — the classic definition of a brittle test. 5. **It is easy to over-reach.** Once the hatch is open, teams start verifying private methods too, and the test suite becomes a second, worse copy of the implementation. ## When it is nonetheless the right call - **Legacy code you cannot restructure yet.** You need a characterisation test before refactoring; reaching into private state is a temporary scaffold that you remove once behaviour is pinned down. - **Third-party classes.** You do not own the visibility, and the only lever is reflective. - **A private property that is genuinely the observable outcome** in a class whose public surface reveals nothing — for example a cached handle whose lifecycle is the point of the test. Even then, prefer exposing the fact through a public query if you own the class. ## Better alternatives when you own the code Widen the property to `internal` so the test source set of the same module can see it (Kotlin's `internal` plus a test-only-usage annotation is a common convention), inject the collaborator that holds the state instead of hiding it, or restructure so the behaviour is observable through the public API. Each of these converts a reflective, name-based coupling into a compiled one. The rule of thumb: reach for `getProperty`/`setProperty` when you cannot change the class, not when you cannot be bothered to.

  • You own the class under test. What would you do instead of `getProperty`?
    Change visibility or design rather than the test. Widening the property to `internal` lets the module's tests see it with full type safety, and injecting the state-holding collaborator makes it observable without reflection at all. Best of all is exposing the outcome through the public API, so the test asserts behaviour instead of storage.
  • The `value` position in `setProperty "x" value ...` — can you use matchers there?
    Yes, it takes an ordinary argument matcher, so `value less(5)` or `value any()` are both valid. That keeps setter stubs and verifications as expressive as normal parameter matching, which matters because you often want to verify only that *some* assignment happened.

saying these in an interview costs you the question

  • Believing MockK can stub private members through the normal expression DSL.
  • Forgetting `recordPrivateCalls = true` and concluding MockK cannot see private access at all.
  • Treating the string name as refactor-safe.
  • Presenting reflective property access as good default practice rather than an escape hatch.
  • Confusing it with mocking private *methods* and inventing an API name for that.

context