After a test calls MockK's mockkStatic on a file facade, what happens to the functions in that facade that you never stubbed, and what exactly does unmockkStatic undo?
answer
- patches the class in place, not a new object
- unstubbed functions call through — spy-like
- whole facade under dispatch, all threads
- unmockkStatic = named classes only
- block form unmocks on exit, even on throw
basics
~20 smockkStatic patches the named class in place, spy-style: only the functions you stub with every are replaced, everything else in that class keeps running its real implementation. unmockkStatic removes the registration for that class and drops its stubs, restoring normal dispatch; other mocked classes are untouched.
solid answer
~50 s`mockkStatic` does not create a mock object. It instruments the **named class in place** so that calls to its methods pass through MockK's dispatch first. The transformation is class-wide, but the substitution is per function: a function you recorded with `every` answers from the stub, and every other function on that facade calls through to the real code. That is why static mocking behaves like a spy rather than a strict mock — unstubbed calls do not throw, they run. That has a scoping consequence: mocking one facade to stub one function silently puts the whole file under MockK's dispatch for the rest of the test. `unmockkStatic("com.acme.GreetingsKt")` removes that class's registration and its stubs, so dispatch goes back to normal for that class only. `unmockkAll()` is the broad hammer — it drops every static, object and constructor registration in the JVM. MockK also offers block-scoped overloads, `mockkStatic(name) { ... }`, which unmock when the block exits, so the mock cannot outlive the code that needed it.
code
kotlin · 8 linesmockkStatic("com.acme.GreetingsKt")
every { greet("bob") } returns "stubbed"
greet("bob") // "stubbed"
greet("ada") // real implementation runs
farewell("x") // real implementation runs
unmockkStatic("com.acme.GreetingsKt")go deeper
Say that mockkStatic changes the real class rather than creating an object, and that unstubbed functions still run for real.
Explain the per-function partiality including argument-level partiality, and what unmockkStatic removes versus unmockkAll.
Lead with blast radius and the passes-alone-fails-in-suite signature, and prefer the block-scoped form or a global teardown as policy.
Frame it as shared mutable process state introduced by a test; argue for narrow facades, a suite-wide cleanup convention, and designing pervasive helpers so they need no patching.
## In-place patching, not a mock object With `mockk<Foo>()` you get a *new object* that implements `Foo` and answers from a stub table; the real class is untouched. `mockkStatic` cannot work that way, because the call site names a class, not an object: `greet("bob")` compiles to a static invocation of `GreetingsKt.greet`. So MockK instruments **that class itself** so its methods route through MockK's dispatch. The class the whole JVM sees is now the patched one. ## Per-function substitution, spy-like fallthrough The patch is installed for the class, but what happens on a given call depends on whether that call matches a recorded stub: - matches a stub -> the recorded answer is produced, the real body never runs; - matches nothing -> the original implementation runs and the result is returned. So static mocking is partial by construction: it behaves like a spy over the whole class. Two things follow. First, you cannot rely on an unstubbed function throwing to tell you that you forgot to stub it — it will quietly do the real thing, and if the real thing hits the network or the clock, your test does too. Second, argument-level partiality is real as well: if you stub `every { greet("bob") }` and the code calls `greet("ada")`, the real function runs for `"ada"`. ## Blast radius of a single call `mockkStatic("com.acme.GreetingsKt")` is often written to stub one function, but it puts the entire facade under MockK dispatch, for every thread in the JVM, until it is removed. Calls made by unrelated production code, by other classes, and by other tests running at the same time all go through the patched class. Nothing about the transformation is scoped to the test class or the calling thread. The practical rule is to narrow what you patch: mock the smallest facade that holds the function you need, do not mock a large utility file that a lot of the codebase calls into, and be aware that stubbing something as pervasive as a time or logging helper affects far more than the code under test. ## What unmockkStatic actually removes `unmockkStatic("com.acme.GreetingsKt")` (and the `KClass` overload) removes MockK's registration for exactly the classes you name and drops the stubs recorded against them; subsequent calls dispatch normally again. It says nothing about mocked objects, mocked constructors or plain `mockk()` instances, and it does not touch other facades you also patched — if a test mocked three files, it must unmock three files, or use the broad form. `unmockkAll()` clears every static, object and constructor registration currently installed in the JVM. It is coarse: in a suite where another mechanism installed a registration deliberately, `unmockkAll` takes that out too. ## Scoping it per test Three shapes exist, in increasing order of safety: 1. **Manual pair** — `mockkStatic(...)` at the start, `unmockkStatic(...)` in teardown. Correct, but the pair can drift apart during edits and an early `return` or a thrown exception in the test body does not skip teardown only if the teardown really is in a teardown hook. 2. **Block-scoped overload** — `mockkStatic("com.acme.GreetingsKt") { ... }` patches the class, runs the block, and removes the patch when the block exits, including on exception. The mock physically cannot outlive the block, which is why it is the preferred form when the mocked call happens in one place. 3. **Broad teardown** — a single `unmockkAll()` after every test. It is the safety net most suites end up with, and it makes tests independent of which specific facades a test patched. ## The diagnostic signature Because the registration is process-global and the fallthrough is silent, the classic symptom of a leak is a test that passes when run alone and fails — or, worse, passes for the wrong reason — when run with the rest of the suite. Any time you see that pattern in a codebase using `mockkStatic`, the first hypothesis should be a facade still patched from an earlier test.
- A test stubs one function on a facade and another function in the same file quietly performs I/O during the test. Does mockkStatic protect you?No. The patch is installed for the class, but only calls matching a recorded stub are substituted; everything else falls through to the real implementation. The I/O function runs for real unless you stub it too. If the point of the test is isolation, you must stub every function you do not want executed, or pick a different seam.
- When would you choose unmockkStatic over unmockkAll?Use unmockkStatic when the test knowingly patched specific facades and other registrations in the JVM belong to someone else, so you want to remove only yours. unmockkAll is the blunt safety net that guarantees no static, object or constructor registration survives the test; it is a reasonable default in a suite where nothing installs long-lived registrations on purpose.
saying these in an interview costs you the question
- Believing mockkStatic creates a mock object that intercepts everything and returns defaults
- Assuming an unstubbed function on a patched facade will throw or return null
- Thinking the patch is confined to the current test class or thread
- Expecting unmockkStatic on one facade to clean up other facades, objects or constructors
- Treating stubbing one argument value as covering all calls to that function