skip to content

A Kotlin class exposes a factory in its companion object, called as Report.of(row). How do you target that companion with MockK so the factory returns a canned Report, and what are the usual reasons such a stub appears to have no effect?

level: middleimportance: should knowfreq 33%

answer

  1. companion is just an object — mockkObject(Report.Companion)
  2. bare class name resolves to the companion instance
  3. named companion: Report.Factory
  4. spy default: wrong overload = real factory runs silently
  5. constructor call / top-level fun = not the companion

basics

~20 s

Patch the companion instance: mockkObject(Report.Companion) — writing mockkObject(Report) works too, since a bare class name with a companion resolves to that companion instance — then every { Report.of(any()) } returns fixture. Stubs miss when a different overload, a constructor call, or a top-level function is what production actually calls.

solid answer

~50 s

A companion object is just an object declaration owned by a class, so the same tool applies: `mockkObject(Report.Companion)`, then `every { Report.of(any()) } returns fixture`, then `unmockkObject(Report.Companion)`. In Kotlin, using the bare class name as a value resolves to its companion instance, so `mockkObject(Report)` is equivalent; a named companion is referenced by its name, e.g. `Report.Factory`. Calls compile to invocations on that companion instance, so production code inside the class that calls `of(...)` unqualified is intercepted too. When a stub "does nothing", it is almost always one of three things: the code calls a *different overload* than the one stubbed and, because object mocks are spies, the real factory quietly runs; the code calls the constructor `Report(...)` directly, which companion patching does not touch; or the factory is actually a top-level or extension function, which is a different mocking entry point entirely.

code

kotlin · 14 lines
kotlin
class Report private constructor(val rows: List<Row>) {
    companion object {
        fun of(row: Row) = Report(listOf(row))
        fun of(row: Row, strict: Boolean) = Report(listOf(row))
    }
}

mockkObject(Report)                       // same as Report.Companion
every { Report.of(any<Row>()) } returns fixture

Report.of(row)                            // fixture
Report.of(row, strict = true)             // real factory — different overload!

unmockkObject(Report)

go deeper

for a junior

Know that a companion is an object and that mockkObject(Report) or mockkObject(Report.Companion) is how you reach it before stubbing with every.

for a middle

Explain that calls compile onto the companion instance so internal calls are intercepted too, and diagnose the overload / constructor / top-level confusions.

for a senior

Decide deliberately what the stubbed factory returns, keep the patched window tight, and recognise a leaked companion patch from the shape of the next test's failure.

for a principal

Treat pervasive companion patching as a signal that construction lacks a seam, and weigh routing creation through an injectable factory for the paths that keep needing it.

## Companion objects are object declarations A companion is an object declaration that happens to be nested in a class and to be reachable through the class name. MockK treats it exactly as it treats any other object: `mockkObject` patches that singleton in place, unstubbed members call through to real code, and `unmockkObject` restores it. Nothing about companions needs a separate mechanism. ## Naming the target All of these refer to the same instance when the class has an unnamed companion: ``` mockkObject(Report.Companion) mockkObject(Report) // bare class name as a value resolves to the companion ``` If the companion has a name — `companion object Factory { ... }` — you reference it as `Report.Factory`. The important point is that you are passing an *instance* (the companion) to `mockkObject`, not a class reference; Kotlin's resolution rules are what make the shorthand read like you are passing the class. Stubbing then uses the natural call syntax: ``` mockkObject(Report) every { Report.of(any()) } returns fixture // ... exercise ... verify { Report.of(row) } unmockkObject(Report) ``` ## Why production code is intercepted, including internal calls A call written `Report.of(row)` compiles to a call on the companion instance, and so does an unqualified `of(row)` from inside `Report`'s own members. Because the patch is on that instance, both are intercepted. This is what makes companion patching useful for legacy code where the factory is called from deep inside the class it belongs to and there is no parameter to inject through. ## The three reasons a stub "does nothing" ### 1. A different overload was called This is by far the most common, and it is invisible because object mocks are **spies**: an unstubbed member runs the real implementation rather than failing. Stubbing `Report.of(row: Row)` does absolutely nothing for a call to `Report.of(row: Row, strict: Boolean)`. The test then constructs a real `Report`, possibly touching a database or a clock, and either passes for the wrong reason or fails somewhere unrelated. When a companion stub seems ignored, list the overloads first. ### 2. The code calls the constructor, not the factory `Report(row)` never goes near the companion. Patching the companion has no effect on constructor invocations at all — intercepting those is a different MockK facility. If the production path you care about builds instances directly, companion patching is the wrong tool for it, and the honest options are to route construction through the factory or to use the constructor-oriented facility instead. ### 3. It is not a companion member at all A "factory" that is a top-level function in a file, or an extension function, is not a member of any object and is not reached by `mockkObject` — those are handled by MockK's static-mocking entry point. Reading the declaration rather than the call site settles this in seconds: `object`/`companion object` member, top-level `fun`, or `fun Row.toReport()` are three different things that all look identical at the call site. ## Return values are yours to build A stubbed factory has to return something. Two habits keep this clean. Return a real, fully-constructed domain object when the type is cheap to build — the test then exercises real behaviour downstream and the assertions read naturally. Return `mockk<Report>()` when the produced object is itself a heavy collaborator whose interactions you want to verify. Returning a relaxed mock is convenient but hides missing expectations, so prefer it only when the produced object is genuinely incidental. ## Scope and cleanup The patch applies to the loaded class in the JVM for as long as it is active, so it is visible to every test running in that process, not just yours. Keep the window narrow: unmock in the same test, either with `unmockkObject` in a `finally`/teardown or by using the block-scoped form `mockkObject(Report) { ... }`, which unmocks on exit. A companion left patched is particularly nasty because the symptom in the *next* test is a factory quietly returning a fixture belonging to somebody else's scenario. ## Quick checklist Target the companion instance; stub every overload the code can reach; confirm the call site is a companion member rather than a constructor or a top-level function; decide deliberately whether the factory returns a real object or a mock; and keep the patched window as short as the test.

  • Your companion stub is in place but the code still gets a real object back. How do you find out why in under a minute?
    Look at the declaration behind the call site rather than the call site itself. If it is a different overload of the same name, the spy default means the real one ran silently — stub that signature too. If it is a constructor invocation or a top-level/extension function, the companion patch was never in the path and you need a different facility.
  • Should a stubbed factory return a real object or another mock?
    Return a real, cheaply-constructed domain object when the test then exercises behaviour on it — assertions stay concrete and you avoid stacking stubs. Return a mock when the produced object is a heavy collaborator whose interactions you want to verify. Avoid reflexively returning a relaxed mock, since it hides expectations you never set.

saying these in an interview costs you the question

  • "You have to mock the class, not the companion" — the companion instance is the target
  • Assuming one every for a name covers all overloads of that name
  • Expecting companion patching to intercept direct constructor calls
  • Confusing a top-level factory function with a companion member
  • Leaving the companion patched past the test and blaming the next failure on flakiness

context