skip to content

MockK's static mocking patches classes in place for the whole JVM. How would you set policy for using it across a large test suite so results stay deterministic?

level: principalimportance: nice to knowfreq 20%

answer

  1. patched class = shared mutable global
  2. narrowest facade, never pervasive helpers
  3. block-scoped form beats setup/teardown pair
  4. parallel execution and static patches conflict
  5. randomize order; alone vs suite

basics

~20 s

Treat a patched facade as shared mutable process state: install it as late and as narrowly as possible, remove it in the same scope that installed it (block-scoped form or a guaranteed teardown), never let static-mocking tests run concurrently with code that calls the same facade, and detect leaks with randomized order plus alone-versus-suite comparison.

solid answer

~60 s

Static mocking is not test-local: it patches a named class for the whole JVM and every thread in it, with silent fallthrough for anything unmatched. So the policy questions are the ones you would ask about any shared mutable global. **Narrow the surface.** Patch the smallest facade that holds the function, and treat pervasive helpers — time, ids, logging, config — as off-limits, because patching them affects the framework and unrelated tests as much as the code under test. **Bind lifetime to a scope.** Prefer the block-scoped `mockkStatic(name) { ... }` form so the patch cannot outlive the code that needed it, and keep a suite-wide teardown as the net. **Serialize.** If tests execute concurrently in one JVM, static-mocking tests must not overlap with anything touching the same facade; give them a tag or a dedicated execution lane. **Detect.** Randomize test order and compare alone-versus-suite runs; "passes alone, fails in the suite" is the leak signature. **Budget.** Keep the list of statically mocked facades small and visible, and review additions.

go deeper

for a junior

Know the core fact — the patch is global to the JVM — and that it must be removed, ideally by the block-scoped form.

for a middle

Describe scoping options and the ordering hazards, and show one concrete cleanup convention you would apply everywhere.

for a senior

Bring the diagnostic angle: randomized order, alone-versus-suite, and serializing tests that patch shared classes.

for a principal

Argue the trade explicitly — a bounded, monitored allowance with a preference for designing seams that make static mocking unnecessary — and say how you would keep the inventory from growing.

## The property that drives everything `mockkStatic` changes a class that the entire process shares. It is not a per-test object, not per-thread, and not scoped to a test class. From the moment it is installed until it is removed, every caller anywhere in that JVM — production code, framework code, another test — hits the patched class. Unmatched calls fall through silently, so a leaked patch does not announce itself; it just changes behaviour somewhere else, sometimes into a false pass. Once you internalise that, the policy writes itself: this is shared mutable global state introduced by tests, and it deserves the same discipline you would apply to a global registry or a system property. ## Narrow the blast radius Two dimensions matter. *Which class.* Patch the file that actually declares the function, not a large shared utility file. If a project's `Utils.kt` accumulates thirty helpers, patching it puts thirty functions under MockK dispatch for one stub. The remedy is structural: helpers you expect to substitute belong in small, single-purpose files with stable JVM names. *Which functions matter.* Because everything unstubbed still runs, patching does not isolate you from side effects elsewhere in the file. If the file does I/O anywhere the code under test can reach, static mocking gives a false sense of isolation. ## Bind the lifetime to a scope Rank the shapes by how hard they are to get wrong: 1. **Block-scoped** `mockkStatic("com.acme.GreetingsKt") { ... }` — the patch is removed when the block exits, including on exception. Nothing can leak past the block, and the reader sees the extent on screen. 2. **Setup/teardown pair** — correct if the teardown really is a teardown hook, but the two calls drift apart during edits and it is easy to add a second facade to setup and forget the matching removal. 3. **Suite-wide net** — a single broad `unmockkAll()` after each test guarantees nothing survives, at the cost of removing registrations somebody may have installed deliberately. Most healthy suites use 1 where the mocked call is localised, and keep 3 as the net. ## Concurrency is the sharp edge If your runner executes tests concurrently inside one JVM, static mocking and concurrency are directly at odds: the patch is visible to all threads, so a test that patches `TimeKt` changes what a test running beside it observes. There is no per-thread mode you can rely on to make that safe. The realistic policies are: keep static-mocking tests in a lane that runs serially; or tag them and exclude them from parallel execution; or forbid parallelism entirely in modules that use them. What is *not* acceptable is parallel execution plus ad-hoc static mocking and a hope that the overlaps never line up — that produces exactly the intermittent, unreproducible failures that erode trust in a suite. ## Detection Because leaks are silent, you need active detection: - **Randomized order.** Order dependence is the observable consequence of a leak; randomizing surfaces it instead of hiding it behind a stable ordering. - **Alone versus suite.** When a test fails in the suite, re-run it alone. Pass-alone/fail-in-suite is the classic signature of a patch installed elsewhere, or of your own patch removed by someone else's cleanup. - **The inverse.** Also watch for tests that pass *only* in the suite — they may be relying on a facade someone else patched, which is the same defect wearing a friendlier face. - **Review the inventory.** Keep the set of statically mocked facades small enough to list. Grep for `mockkStatic` periodically; growth in that list is growth in coupling between unrelated tests. ## The judgement call Static mocking buys you a test you could not otherwise write against code that calls a top-level function directly. It costs process-global state, order sensitivity, hostility to parallelism, and a string binding that refactoring breaks silently. That trade is worth taking occasionally and at the edges — a legacy call site you cannot change today, a third-party helper. It is not worth taking as the routine way to isolate your own code, and a suite where the `mockkStatic` count keeps climbing is telling you something about its seams rather than about MockK. The policy to state in an interview: allowed, bounded, scoped, serialized, and monitored — with a standing preference for a seam that needs none of it.

  • A test in your suite passes alone and fails when the whole suite runs. How does static mocking enter your hypothesis list?
    It goes near the top. A patched facade installed by an earlier test and never removed changes behaviour for everything after it, and conversely a broad cleanup elsewhere can remove the patch your test relies on. I would grep the suite for static mocking, re-run with a different seed for the test order to see if the failure follows a specific predecessor, and check whether cleanup is guaranteed rather than best-effort.
  • Is there a case where you would accept static mocking of a widely-used helper anyway?
    Rarely, and with fences. If a legacy call site cannot be changed in the current piece of work, I would use the block-scoped form so the patch lasts only for the call, keep the test in a serial lane, and record the debt so the seam gets fixed. What I would not accept is that pattern spreading, because each new instance multiplies the ordering constraints between otherwise unrelated tests.

saying these in an interview costs you the question

  • Treating a static patch as if it were confined to the test that installed it
  • Running tests concurrently in one JVM while freely patching shared facades
  • Relying on a cleanup call at the end of the test body that an early failure can skip
  • Assuming a silent leak will show up as an error rather than as a wrong pass
  • Letting the inventory of statically mocked classes grow without review

context