skip to content

Code under test builds three instances of a class whose constructor you mocked with MockK, and each instance calls the same method once. What does verify(exactly = 1) { anyConstructed<T>().method() } do, and how do you assert what you meant?

level: seniorimportance: should knowfreq 30%

answer

  1. records are per class, not per instance
  2. 3 instances x 1 call = 3 recorded calls
  3. exactly=3 works but couples to instance count
  4. prefer argument matchers to counts
  5. constructedWith is the only partitioning

basics

~20 s

It fails: recorded calls are pooled per class, so MockK counts three calls, not one per instance. Assert the aggregate (exactly = 3), or split the instances by constructor arguments with constructedWith, or distinguish the calls by their own argument matchers.

solid answer

~50 s

Constructor mocking records against the **class**, not per object. `anyConstructed<T>()` is one shared handler, so three instances each calling `method()` once produce three recorded calls on that handler. `verify(exactly = 1) { anyConstructed<T>().method() }` therefore fails with a count mismatch, and a candidate who expected "once per instance" has the wrong mental model. What you can express: - **Aggregate counts** — `verify(exactly = 3) { anyConstructed<T>().method() }`. Honest, but it silently couples the test to how many objects the production code creates. - **Method arguments** — `verify(exactly = 1) { anyConstructed<T>().send("a@b", any()) }`. Usually the best answer: assert about the *calls that matter* rather than about instances. - **Constructor arguments** — MockK's `constructedWith` narrows to instances built with particular constructor arguments, which is the only way to separate instances at all. What you cannot do is address one instance by identity: the test never holds those objects and MockK keeps no per-instance record.

code

kotlin · 8 lines
kotlin
mockkConstructor(MailClient::class)
every { anyConstructed<MailClient>().send(any(), any()) } returns true

notifier.notifyAll(listOf(ada, bob, cleo))

// verify(exactly = 1) { anyConstructed<MailClient>().send(any(), any()) } // fails: 3 recorded
verify(exactly = 3) { anyConstructed<MailClient>().send(any(), any()) }
verify(exactly = 1) { anyConstructed<MailClient>().send("ada@x", any()) }

go deeper

for a junior

Know that recorded calls are pooled per class, so the count is across all instances the code created.

for a middle

Show the three assertion options — aggregate count, argument matchers, constructedWith — and why counts are the most brittle.

for a senior

Discuss what is inexpressible (identity, per-instance ordering) and read a multiple-of-expected count as evidence about the construction site.

for a principal

Treat the need for per-instance distinctions as a design signal to inject a factory instead of instrumenting the constructor.

## Where the surprise comes from Every other mock you use in MockK is an object: you create it, you stub it, you verify it, and counts are naturally per object. Constructor mocking breaks that intuition because the test never obtains the objects. MockK's instrumentation is installed on the class, and the stub table and the recorded-call log live with the class too. `anyConstructed<T>()` is the way to address that class-level record. So with ```kotlin users.forEach { MailClient(config).send(it.email, body) } ``` three `MailClient` objects exist, but MockK has recorded three `send` calls on **one** handler. `verify(exactly = 1)` sees 3 and fails; `verify(exactly = 3)` passes; a plain `verify { ... }` (at-least-once) passes as well. ## Choosing what to assert **Aggregate count.** `verify(exactly = 3)` states the truth, but think about what you have coupled the test to: the number of objects the implementation happens to create. If a refactor batches two users into one client, the test breaks without any behaviour changing. Aggregate counts are fine when the number is itself the requirement ("one client per message, no reuse") and a poor idea otherwise. **Argument-level assertions.** Usually the healthiest translation of "each user got a mail": ```kotlin verify(exactly = 1) { anyConstructed<MailClient>().send("ada@x", any()) } verify(exactly = 1) { anyConstructed<MailClient>().send("bob@x", any()) } ``` These say what the system must do and stay true regardless of how many client objects were built. The verification is still class-wide, but the matcher narrows it to the calls you care about. **Constructor-argument partitioning.** When instances genuinely differ by how they were constructed — a client per region, a parser per format — `constructedWith` lets you record and verify against only the instances built with matching constructor arguments, which is as close to per-instance addressing as the feature gets. ## What is simply unavailable - **Identity.** You cannot say "the second one". No handle to the objects exists on the test side, and MockK does not expose a per-instance record you can index. - **Per-instance isolation of stubs.** A stub on `anyConstructed<T>()` answers for all instances; you cannot make the first return `true` and the second `false` by position. Sequencing terminators can vary the answer across successive *calls*, but that is by call order, not by instance. - **Ordering across instances.** Ordered verification sees one merged stream, so "instance A did X before instance B did Y" is not expressible. ## Failure-message reading A count mismatch here reports the calls MockK recorded for the class. When you see a number that is an exact multiple of what you expected, the near-certain explanation is that the production code constructs more instances than the test assumed and every one of them is folded into the same record. That is a useful diagnostic in the other direction too: an unexpectedly *high* count is often the first evidence that a loop creates a collaborator per iteration. ## Design signal When a test has to reason hard about which of several internally created instances did what, the test is telling you the production code has an invisible collaborator with an unclear lifecycle. Aggregate verification is workable; needing per-instance distinctions is the point where injecting a factory — which the test *can* hold and assert against per call — becomes the cheaper design. Say that out loud in an interview: it is the difference between knowing the API and knowing when the API is the wrong tool.

  • Can you make the first constructed instance return one value and the second another?
    Not by instance. Stubs live on the class handler, so anything you record applies to every constructed instance. You can vary the answer across successive calls with a sequencing terminator, but that keys on call order, not on which object made the call. If the distinction really is per instance, partition by constructor arguments with constructedWith, or inject a factory the test controls.
  • Your verification count comes back as a multiple of what you expected. What is the likely cause?
    The production code constructs more instances than you assumed and all of them fold into the same class-level record — typically a collaborator built inside a loop. Confirm by checking the construction site, then decide whether the count itself is a requirement worth asserting or whether an argument-level assertion expresses the intent better.

saying these in an interview costs you the question

  • Expecting each constructed instance to have its own call count
  • Trying to obtain a reference to a constructed instance to verify it individually
  • Asserting instance counts and calling the resulting brittleness a MockK limitation
  • Believing ordered verification can separate calls made by different constructed instances
  • Assuming a stub can be scoped to one instance without constructedWith

context