How would you set the mock-isolation policy for a large Kotlin test suite that uses MockK — fresh mocks per test, clearMocks/clearAllMocks, or unmockkAll teardown — and what are the tradeoffs of each?
answer
- fresh per test = default
- clearMocks only for shared/phased
- clear ≠ unmockk
- scoped blocks beat remembered teardown
- one enforced place for teardown
basics
~20 sDefault to creating mocks per test so no state can survive. Use clearMocks only for genuinely shared or mid-test resets. Make unmockkAll (or scoped mockk* blocks) mandatory for anything patched globally, in one enforced place.
solid answer
~50 sI would set three tiers. **Tier 1 — fresh mocks per test, as the default.** A mock that does not survive the test cannot leak. This removes the entire category of stale-stub and accumulated-count bugs, and it costs almost nothing. **Tier 2 — clearMocks, by exception.** Only for doubles that genuinely must be shared (expensive fixtures, a double injected into a long-lived component) or for mid-test resets such as `clearMocks(repo, answers = false)` to zero counts between phases. Clearing is ordering-sensitive: placed after per-test stubbing it silently wipes them, so it lives in exactly one documented place. **Tier 3 — mandatory teardown for global patches.** `mockkObject`/`mockkStatic`/`mockkConstructor` install JVM-wide registrations that no test boundary removes, and `clearAllMocks` does not undo them. Prefer the scoped block forms so the patch cannot outlive its test; where that is impossible, `unmockkAll()` in one shared teardown. Anti-pattern to ban: a blanket `clearAllMocks()` in setup used as a substitute for understanding what actually leaks.
go deeper
Say the simple rule: create mocks fresh in each test, and undo anything you patched globally.
Lay out the tiers and be precise that clearing resets state while unmockk removes instrumentation.
Add the operational detail — ordering sensitivity of clearing, targeted versus blanket unmocking, and one enforced teardown location.
Own the property being protected (no order dependence), pick mechanisms that make violations structurally impossible, and make the policy observable through review and execution-order variation.
## What the policy has to achieve One property: **no test's outcome depends on what ran before it.** Everything else — how much you clear, where teardown lives — is a means to that end. Policies fail when they optimise for ritual ("always call clearAllMocks") instead of for the property. ## Tier 1: recreate rather than reset The strongest isolation is a double that does not survive the test at all. If mocks are created in per-test setup, there is no stub table to go stale and no call log to accumulate. Mock creation is cheap relative to almost anything else a test does. This should be the default, and a suite that follows it needs no `clearMocks` at all for ordinary collaborators. A `clearMocks` call at the top of a test whose mocks are already created per test is dead code — it signals that someone did not know where the state lived, and it should be deleted in review. **Tradeoff**: none worth mentioning for ordinary mocks; it only becomes a question when a double is expensive to build or is wired into a fixture that itself is expensive. ## Tier 2: clearMocks, deliberately and narrowly Two legitimate cases: - **Shared fixture.** A double built once for a class because construction is genuinely costly, or because it is injected into a long-lived component that cannot be rebuilt per test. Then a per-test `clearMocks` restores the illusion of freshness. - **Mid-test reset.** A test with distinct phases that wants counts from zero without re-declaring stubs: `clearMocks(repo, answers = false)`. **Tradeoffs.** Clearing is a plain function call and therefore ordering-sensitive — if it runs after the code that configures stubs, it removes them and the failure ("no answer found") points at a line where the stub is plainly visible. It is also incomplete by nature: it resets state but never removes instrumentation, so it can never be the answer for a leaked global patch. And a blanket clear hides which state actually mattered, which makes the next isolation bug harder to reason about. Rule: one place per suite where clearing happens, with a comment saying which shared fixture it serves. ## Tier 3: global patches need structural teardown `mockkObject`, `mockkStatic` and `mockkConstructor` are installations in a process-wide registry, not values. No test boundary removes them, and `clearAllMocks` explicitly does not. This is the tier where a policy earns its keep. Preference order: 1. **Don't patch.** Inject the collaborator instead. Most `mockkObject` calls exist because a singleton is reached directly from production code; fixing that removes the test problem and improves the design. 2. **Scoped blocks.** MockK 1.13's `mockkObject(X) { ... }` / `mockkStatic("...") { ... }` / `mockkConstructor(X::class) { ... }` remove the registration on exit, including on failure. Nothing to remember, nothing to forget. 3. **Symmetric teardown in one shared place.** Targeted `unmockkObject`/`unmockkStatic` where the test knows exactly what it installed; `unmockkAll()` where a sweep is acceptable. Note that a blanket sweep also tears down patches installed by an enclosing fixture, which is a real hazard in suites with shared base classes. **Tradeoff of blanket unmockkAll**: it is safe and forgiving, but it makes setup and teardown asymmetric, so nobody can tell from a test what it actually patched. ## Making the policy stick - Put teardown in one shared base fixture rather than in individual tests; a rule that depends on everyone remembering will be broken within a month. - Review new occurrences of the global patching functions — the count should be flat or falling. - Randomising or varying execution order surfaces order dependence early instead of on the day someone adds a test in the middle. - When an isolation bug appears, fix the *category*, not the instance: if one forgotten `unmockkStatic` slipped through, the answer is scoped blocks, not one more teardown line. ## The summary I would give "Fresh mocks by default, clearing only for shared or phased fixtures and only in one place, and global patches either scoped to a block or torn down in a single enforced teardown. Clearing resets state; only unmocking removes instrumentation — a policy that confuses the two will keep producing order-dependent failures."
- Someone proposes putting clearAllMocks() in a base-class setup method for every test. What do you say?It is ritual rather than isolation. It does nothing for suites that already create mocks per test, it cannot undo mockkObject/mockkStatic registrations — the leaks that actually cause order dependence — and it is ordering-sensitive, so if it ever runs after per-test stubbing it wipes those stubs. I would rather have fresh mocks by default and one enforced teardown for global patches.
- When is sharing a mock across tests actually justified?When constructing it, or the fixture holding it, is genuinely expensive — for example a double wired into a heavyweight component that is built once per class — and re-creating it per test would dominate the suite's runtime. In that case share it deliberately, reset it with clearMocks in one documented place, and accept that you have traded isolation for speed.
saying these in an interview costs you the question
- Treating clearAllMocks in setup as a universal isolation guarantee
- Not distinguishing state resetting from removing instrumentation
- Relying on every author to remember teardown instead of scoping the patch
- Sharing mocks across tests for convenience rather than for a measured cost
- Fixing individual order-dependent tests without addressing the category