skip to content

What does MockK's `callOriginal()` do inside an answer block, on which kinds of mocks does it work, and why would you use it instead of simply leaving the function unstubbed?

level: seniorimportance: should knowfreq 35%

answer

  1. callOriginal() = "proceed" to the real implementation
  2. needs an original: spy, patched object/static/constructor
  3. plain mockk has no original ⇒ MockKException
  4. its value: CONDITIONAL and WRAPPING stubs
  5. runs real production code — partial mocking smell

basics

~20 s

callOriginal() invokes the real implementation behind the intercepted call and returns its result. It needs an original to call — a spy, or a real object/class/static/constructor that MockK has patched — and fails on a plain interface mock. Use it to keep real behavior while still recording, wrapping or overriding the call conditionally.

solid answer

~60 s

`callOriginal()` is a member of MockK's answer scope: it dispatches the intercepted invocation to the underlying real implementation and hands you its result, so you can return it, transform it, or run code around it. It requires an *original* to exist. That is true for a `spyk` wrapping a real instance, and for objects, statics and constructors that MockK has patched in place. A plain `mockk<SomeInterface>()` has no real body behind it, so calling it there fails. Why not just leave the call unstubbed? Because the two differ in the cases that matter: ```kotlin every { service.compute(any()) } answers { if (firstArg<Int>() < 0) 0 else callOriginal() } ``` Here the stub is *conditional* — real behavior for normal input, forced behavior for the edge case — which you cannot express by not stubbing. It also lets you wrap the real call (timing, logging, mutating the result) and, on a patched object or static, is often the only way to restore real behavior for one member while others stay mocked.

code

kotlin · 9 lines
kotlin
val pricing = spyk(PricingService(rates))

every { pricing.quote(any()) } answers {
    val req = firstArg<Request>()
    if (req.currency == "XXX") Quote.unavailable() else callOriginal()
}

assertTrue(pricing.quote(Request("XXX")).isUnavailable)
assertEquals(realQuote, pricing.quote(Request("EUR")))

go deeper

for a junior

Say it calls the real implementation from inside the stub, and that it only makes sense where a real implementation exists, such as on a spy.

for a middle

Add where the original exists — spies and patched objects, statics and constructors — and that a plain mock fails, plus the conditional-stub use case.

for a senior

Discuss wrapping and selective restoration, the fact that recording still happens, and the risk of real side effects and self-call interception inside the original.

for a principal

Position partial mocking as a design signal: acceptable for code you cannot change, a prompt to split responsibilities in code you own, with a team rule about where it is allowed.

## What it is `callOriginal()` is available on MockK's answer scope, so it can be used anywhere a block-form answer can: `answers { }`, `andThenAnswer { }`, and the coroutine-aware block form. It executes the **original implementation** for the invocation MockK intercepted and returns its value. Think of it as "proceed" in interception terms. ## Where an "original" exists MockK intercepts calls in different ways, and only some of them leave a real implementation to proceed to: - **Spies** (`spyk(realObject)` / `spyk<T>()`) — the whole point of a spy is that a real implementation sits underneath, so `callOriginal()` is available for any member. - **Objects, statics and constructors patched in place** — when MockK has replaced the behavior of a real object, a file facade's functions, or constructed instances, the original bodies still exist and can be invoked. - **Plain `mockk<T>()`** — there is nothing underneath. For an interface there is no implementation at all; for a class, MockK created an instance whose members are entirely intercepted. `callOriginal()` here has no original to dispatch to and fails with a MockK exception. This is the single most common surprise. Abstract members are the same story: there is no body to call, so asking for the original is meaningless. ## Why not simply not stub it? On a spy, an unstubbed member already runs the real code, so `callOriginal()` looks redundant. It stops being redundant as soon as the answer is *conditional* or *wrapped*: **Conditional overriding** — real behavior in general, forced behavior for a specific input: ```kotlin every { pricing.quote(any()) } answers { val req = firstArg<Request>() if (req.currency == "XXX") Quote.unavailable() else callOriginal() } ``` Not stubbing gives you the real path for *all* inputs; a plain `returns` gives you the fake path for all inputs. Only an answer block with `callOriginal()` splits the input space. **Wrapping** — run the real code but observe or adjust around it: ```kotlin every { parser.parse(any()) } answers { val result = callOriginal() result.copy(source = "test") } ``` **Selective restoration on patched objects/statics** — once you have patched an object, you may want most of it mocked but one member behaving normally. `every { Config.load() } answers { callOriginal() }` puts one member back without unpatching everything. **Failure injection with real behavior for the rest of the sequence** — combined with a chain, e.g. throw on the first call and proceed for the rest. ## Costs and cautions - **You are running production code inside a test double.** Everything the original does — I/O, clock reads, database access, other collaborators — happens for real. If the original was the reason you introduced a double at all, calling it back defeats the purpose. - **Partial mocking is a design signal.** Needing real behavior for some members and fake behavior for others on the same object often means the class has two responsibilities that want splitting. Reach for `callOriginal()` when you are testing code you cannot easily change (legacy, third-party base classes); treat it as a smell in code you own. - **Recording still happens.** The call is intercepted and recorded before the original runs, so verifications see it — that is precisely what a spy is for. - **Recursion and self-calls.** If the original implementation calls other members of the same object, those calls are also intercepted, which can produce surprising interactions with your other stubs on the same double. Keep the set of stubs on a partially-mocked object small and explicit. ## Typical exam answer "`callOriginal()` proceeds to the real implementation from inside an answer block. It works where a real implementation exists — spies, and objects, statics or constructors MockK has patched — and fails on a plain mock because there is nothing underneath. I use it for conditional stubs: real behavior in general, forced behavior for one input, or to wrap the real result. I avoid it as a routine tool, because running production code inside a double is usually a sign the class wants splitting."

  • What happens if you call callOriginal() inside an answers block on a plain mockk<SomeInterface>()?
    It fails with a MockK exception, because there is no original implementation to dispatch to — an interface mock has no body behind its members. callOriginal() is only meaningful where a real implementation exists: a spy over a real instance, or an object, static or constructor that MockK has patched in place.
  • If an unstubbed member of a spy already runs the real code, when is callOriginal() not redundant?
    Whenever the answer is conditional or wrapped. A conditional stub returns a forced value for some inputs and the real result for the rest, which cannot be expressed by leaving the member unstubbed. Wrapping — timing, logging, or adjusting the real result — likewise needs the interception plus an explicit proceed.

It is the super call of stubbing: you take over the method, then decide whether to delegate to the implementation you displaced.

saying these in an interview costs you the question

  • Expecting callOriginal() to work on a plain mockk of an interface.
  • Thinking it is just a synonym for leaving the member unstubbed on a spy.
  • Assuming the call is not recorded because the real code ran.
  • Using it routinely to keep half a class real, without noticing that is a design signal.
  • Forgetting that the original executes real side effects such as I/O or clock reads.

context