skip to content

What does MockK's constructedWith add on top of anyConstructed, and how are its arguments written compared with the matchers you use inside an every block?

level: seniorimportance: should knowfreq 22%

answer

  1. filter constructed instances by constructor args
  2. matcher objects, not the every-DSL
  3. EqMatcher / OfTypeMatcher / ConstantMatcher
  4. one matcher per constructor parameter
  5. couples the test to the constructor signature

basics

~20 s

constructedWith narrows a stub or verification to the constructed instances built with matching constructor arguments, instead of all of them. Its filter is written with MockK matcher objects such as EqMatcher and OfTypeMatcher, not with the every-block any()/eq() DSL.

solid answer

~50 s

`anyConstructed<T>()` addresses every instance of a constructor-mocked class. `constructedWith<T>(...)` addresses only those whose **constructor arguments** match the filter you give — the one way MockK lets you tell constructed instances apart, since the test never holds them. The important mechanical detail is the argument style. Inside `every { }` you write matchers as DSL calls (`any()`, `eq(x)`) that MockK records while it replays the block. The constructor filter is not part of that recorded call, so it is expressed with **matcher objects** instead — for example `EqMatcher("eu")` for a specific value or `OfTypeMatcher<String>(String::class)` for any value of a type. Mixing the two up (`constructedWith<T>(any())`) is the standard mistake. So a typical use is one stub per region, format or endpoint, each keyed on the constructor argument that distinguishes them, with verification written the same way. Cleanup is unchanged: it is still one `mockkConstructor`/`unmockkConstructor` pair for the class.

code

kotlin · 9 lines
kotlin
mockkConstructor(RegionClient::class)

every { constructedWith<RegionClient>(EqMatcher("eu")).ping() } returns true
every { constructedWith<RegionClient>(EqMatcher("us")).ping() } returns false

assertTrue(service.pingRegion("eu"))

verify { constructedWith<RegionClient>(EqMatcher("eu")).ping() }
unmockkConstructor(RegionClient::class)

go deeper

for a junior

Know that constructedWith narrows constructor mocking to instances built with particular arguments, while anyConstructed covers all of them.

for a middle

Show the matcher-object argument style and explain why it differs from the every-block DSL.

for a senior

Discuss when the filter cannot help, how to test whether a filter is too narrow, and the coupling it creates to the constructor signature.

for a principal

Treat several filtered stubs as a smell pointing at a missing factory abstraction, and note the version availability before standardising on it.

## The gap it fills Constructor mocking pools everything by class: one stub table, one recorded-call log, no instance identity. That is fine when all constructed instances play the same role, and unhelpful when they do not — a client constructed per region, a parser constructed per format, a connection constructed per endpoint. The only thing that distinguishes those objects from the test's point of view is **how they were constructed**. `constructedWith<T>(...)` keys on exactly that. It returns a handle you use in `every` and `verify` the same way as `anyConstructed<T>()`, but the stubs and verifications attached to it apply only to instances whose constructor arguments match the filter. ## Why the argument style differs Inside `every { mock.send(any(), eq("x")) }`, MockK executes the block, watches the calls made on mocks, and treats `any()`/`eq()` as recorded matcher markers for that call. The constructor filter is different: it is not a call being recorded, it is a selector evaluated when MockK decides which handle a constructed object belongs to. So it takes **matcher instances** directly: - `EqMatcher(value)` — the constructor argument equals this value; - `OfTypeMatcher<String>(String::class)` — the argument is of that type, whatever the value; - `ConstantMatcher<Any>(true)` — matches anything in that position. One matcher per constructor parameter, in declaration order. Writing `constructedWith<T>(any())` there is the recurring error, and the reason to remember the split is simple: the DSL matchers only exist during a recording block. ## How it composes with anyConstructed Both handles address the same instrumented class, so `mockkConstructor(T::class)` is still what installs the transformation and `unmockkConstructor(T::class)` still what removes it. Within a test you can: - stub `constructedWith<Client>(EqMatcher("eu"))` and `constructedWith<Client>(EqMatcher("us"))` differently, giving genuinely different behaviour to the two flavours of instance; - verify against a filtered handle to assert that the client built for one region — and only that one — was used; - fall back to `anyConstructed<Client>()` for the calls where the distinction does not matter. Bear in mind the spy-like fallthrough is unchanged: an instance that matches no stub, on a filter or on `anyConstructed`, runs the real method. ## Where it does and does not help It helps when the constructor argument is the meaningful discriminator and is stable. It does not help when: - the instances are constructed identically and differ only by usage order — there is nothing to key on; - the discriminating value is computed inside the constructor rather than passed to it; - the class has several constructors or many defaulted parameters, in which case the effective argument list at the call site may not be the one you expect. That last point is worth checking with a quick experiment before assuming the filter is broken: widen the filter to a match-anything matcher, confirm the stub takes effect, then narrow it. ## Cost side A filtered constructor stub encodes the collaborator's construction site *and* its constructor signature into the test. Both are implementation detail. Adding a parameter to the constructor, or reordering two, silently changes which instances match — one more reason this belongs at the edges of a codebase rather than in its everyday testing style. When you find yourself writing several filtered stubs to model a family of collaborators, a factory the test can inject and control usually expresses the same thing with far less coupling. ## Version note `mockkConstructor` and `anyConstructed` are the older, universally available part of this feature; `constructedWith` came later and is what current MockK 1.13.x provides. On an older MockK than the one your project pins, check that the symbol exists before designing a test around it.

  • Why can't you write constructedWith<T>(any()) the way you would inside an every block?
    The DSL matchers any() and eq() are recorded while MockK replays an every or verify block; they are meaningful only during that recording. The constructor filter is a selector evaluated outside that recording, so it takes matcher instances directly — for example ConstantMatcher to match anything or OfTypeMatcher to match by type. Using the DSL form there is the most common mistake with this API.
  • Two constructed instances differ only in the order they are created, not in their constructor arguments. How do you give them different behaviour?
    constructedWith cannot help, because there is nothing to key on. You can vary answers across successive calls with a sequencing terminator, which keys on call order rather than instance, or accept aggregate assertions. If the distinction is genuinely important to the behaviour under test, the honest fix is to inject a factory the test controls so each produced collaborator is a real mock you hold.

saying these in an interview costs you the question

  • Passing any() or eq() into constructedWith instead of matcher objects
  • Thinking constructedWith replaces mockkConstructor rather than refining what anyConstructed addresses
  • Expecting it to distinguish instances by creation order rather than by constructor arguments
  • Assuming a filtered stub makes unmatched instances inert — they still run real methods
  • Ignoring that the filter pins the test to the collaborator's constructor signature

context