skip to content

How do slot() and capture() work in MockK, and when would you capture an argument instead of just matching it?

level: seniorimportance: should knowfreq 55%

answer

  1. slot<T>() container, capture(slot), read slot.captured
  2. captures generated/unpredictable values
  3. MutableList capture = all calls in order
  4. every-capture feeds answers { }
  5. slot holds last value; obeys all-or-nothing rule

basics

~10 s

A slot is a container. You pass capture(slot) where an argument goes, and MockK stores the real value the code passed in. Afterwards you read slot.captured to assert on or inspect that value.

solid answer

~50 s

slot<T>() creates a CapturingSlot<T>. Inside every or verify you write capture(theSlot) in the argument position; MockK records the actual argument into the slot. You then read theSlot.captured to make assertions about an object your test couldn't construct beforehand — e.g. an entity with a generated id or timestamp. capture follows the same all-or-nothing matcher rule. For multiple invocations, use a MutableList<T> with capture(list) to collect every call's argument in order, then assert on the list. Capturing is preferable to a complex eq() when the expected value is hard to express up front or you only care about a few fields. A subtlety: if both every and verify capture into the same slot, the slot holds the last captured value; and capturing in every runs at call time while capturing in verify runs after the action. Use answers { slot.captured } to echo a captured value back as the stub result.

code

kotlin · 11 lines
kotlin
val saved = slot<User>()
val repo = mockk<UserRepository>(relaxed = true)
UserService(repo).register("Ada")

verify { repo.save(capture(saved)) }
assertEquals("Ada", saved.captured.name)
assertTrue(saved.captured.id > 0)

// Capture all calls:
val ids = mutableListOf<Long>()
verify { repo.findById(capture(ids)) }

go deeper

for a junior

Understands a slot stores an argument you read via slot.captured.

for a middle

Uses slot() in verify to assert on a generated value and knows the list form for multiple calls.

for a senior

Distinguishes capture timing in every vs verify and feeds captured values into answers { }; knows slot keeps only the last value.

for a principal

Guides when capturing improves readability vs. over-coupling tests, and standardizes capture usage in the test suite.

## The problem capturing solves Sometimes you can't write `eq(expected)` because you don't know the exact value in advance — the code under test builds an object with a generated id, a `now()` timestamp, or a derived field. **Capturing** lets you grab the *actual* argument and assert on the parts you care about. ## `slot()` — a single value container `slot<T>()` returns a `CapturingSlot<T>`. You place `capture(slot)` where the argument goes; MockK writes the real argument into the slot. Read it via `slot.captured`. ```kotlin val saved = slot<User>() val repo = mockk<UserRepository>(relaxed = true) UserService(repo).register("Ada") verify { repo.save(capture(saved)) } assertEquals("Ada", saved.captured.name) assertTrue(saved.captured.id > 0) // generated id we couldn't predict ``` `slot.isCaptured` tells you whether anything was captured. `capture()` obeys the **all-or-nothing matcher rule**: if a call uses `capture`, all its other arguments must be matchers too. ## Capturing many calls — `MutableList` For a method called multiple times, capture into a list to keep every argument in order: ```kotlin val ids = mutableListOf<Long>() verify { repo.findById(capture(ids)) } assertEquals(listOf(1L, 2L, 3L), ids) ``` ## Capturing in `every` vs `verify` - In `verify`, capture happens **after** the action runs — you inspect what was passed historically. - In `every`, capture happens **at call time**; you can then use the captured value to compute the stub's result: ```kotlin val slot = slot<User>() every { repo.save(capture(slot)) } answers { slot.captured.copy(id = 99L) } ``` Here `answers { }` reads `slot.captured` to echo back an enriched object — the captured argument feeds the dynamic answer. ## Slot semantics - A single `slot()` holds only the **last** captured value; for history, use the list form. - If both an `every` and a `verify` capture into the same slot, the final read reflects the most recent capture. ## When to capture vs. match - Use `eq()`/`any()` when you can express the expectation directly. - Use `capture()` when the value is generated, large, or you only want to assert on a subset of fields — capturing keeps the test readable instead of building a giant `eq` expected object. ## Pitfall summary - Don't read `slot.captured` before the capturing call actually happened (it throws). - Capturing every argument 'just in case' bloats tests; capture only what you assert on.

  • You need every argument of repeated calls, not just the last. How?
    Pass a MutableList<T> to capture(list); MockK appends each call's argument in order.
  • How can a captured value influence the stub's return value?
    Capture in every and use answers { slot.captured ... } to compute the result from the captured argument.

A slot is like a evidence bag: as the call passes by, you snatch the actual argument into the bag and examine it later.

saying these in an interview costs you the question

  • Reading slot.captured before the capturing call ran
  • Expecting a single slot to retain every call's value
  • Mixing capture with raw literals (breaks all-or-nothing rule)
  • Capturing arguments you never assert on
  • Confusing capture (records value) with any() (only matches)

context