skip to content

MockK's `mockk()` takes a `moreInterfaces` parameter. What problem does it solve, and how do you stub or verify a call that belongs to one of those extra interfaces?

level: seniorimportance: nice to knowfreq 16%

answer

  1. one mock, extra interfaces on the runtime type
  2. fixes `if (x is AutoCloseable)` branches
  3. vararg KClass — pass as a named argument
  4. cast inside every/verify to reach extra members
  5. smell check: runtime type test in production code

basics

~20 s

It makes one mock implement additional interfaces beyond the mocked type, so code that does an is/as check — such as closing a collaborator that happens to be AutoCloseable — takes the intended branch. To stub or verify those members you cast the mock to that interface.

solid answer

~50 s

Sometimes production code branches on a capability rather than the declared type: `if (source is AutoCloseable) source.close()`, or a framework that checks whether a handler also implements a lifecycle interface. A mock of only the declared type fails that check, so the branch you wanted to test is never entered. `mockk<Repository>(moreInterfaces = arrayOf(AutoCloseable::class))` produces a single mock instance that is both a `Repository` and an `AutoCloseable`. Kotlin's type system still only sees the reified type, so to touch the extra members you cast: ```kotlin verify { (repo as AutoCloseable).close() } ``` It takes a vararg of `KClass`, so pass several when the code checks several capabilities. The parameter comes after `relaxed` in the signature, so use it as a named argument. The honest caveat: needing it usually signals that production code is doing a runtime type test. If you own that code, an explicit capability in the API is often the better fix — `moreInterfaces` is the right tool when you do not.

code

kotlin · 16 lines
kotlin
class Drainer {
    fun drain(source: Source) {
        source.readAll()
        if (source is AutoCloseable) source.close()
    }
}

val source = mockk<Source>(
    moreInterfaces = arrayOf(AutoCloseable::class),
    relaxUnitFun = true,
)
every { source.readAll() } returns "data"

Drainer().drain(source)

verify { (source as AutoCloseable).close() }

go deeper

for a junior

Know that it exists: it lets one mock also implement extra interfaces so runtime is checks in the code under test pass.

for a middle

Be able to write it — named argument, arrayOf(X::class) — and cast inside every/verify, remembering that Unit members still need stubbing or relaxUnitFun.

for a senior

Recognise the shape of code that needs it (capability checks around resources and framework SPIs) and pair it with the right relaxation option; know it is interfaces only.

for a principal

Read it as a design signal: a hidden runtime type contract on a collaborator. Decide whether to make the capability explicit in the API or accept the mock option because the branching code is third-party.

## The problem Some code does not just call a collaborator — it *inspects* it: ```kotlin fun drain(source: Source) { source.readAll() if (source is AutoCloseable) source.close() // capability check } ``` This shape is common around resources (`Closeable`/`AutoCloseable`), around framework SPIs ("if the handler also implements `Ordered`, respect it"), and around legacy Java APIs that use marker interfaces. If your test creates `mockk<Source>()`, the mock implements `Source` and nothing else. The `is AutoCloseable` check is false, the branch is skipped, and the test that was supposed to prove "we close the source" silently proves nothing — or worse, `verify { ... close() }` fails and you spend twenty minutes suspecting MockK. ## What `moreInterfaces` does `moreInterfaces` is a **creation option**, a `vararg KClass<*>` in the `mockk()` signature: ```kotlin mockk<T>( name: String? = null, relaxed: Boolean = false, vararg moreInterfaces: KClass<*>, relaxUnitFun: Boolean = false, block: T.() -> Unit = {}, ): T ``` It tells MockK to build **one mock instance that additionally implements the listed interfaces**. It is one object with a wider runtime type — not a second mock, and not a wrapper. Every `is`/`as` check against those interfaces now succeeds, and calls to their members are intercepted by the same MockK dispatcher as the primary type's members. Because the parameter sits after `relaxed` in the parameter list, always pass it by name: ```kotlin val repo = mockk<Repository>( moreInterfaces = arrayOf(AutoCloseable::class), relaxUnitFun = true, ) ``` ## Stubbing and verifying the extra members The compiler still types the expression as the reified type `T` — only the *runtime* type is wider. So to reach a member of an extra interface you cast, inside the `every` / `verify` block: ```kotlin every { (repo as AutoCloseable).close() } just Runs verify { (repo as AutoCloseable).close() } ``` A readable alternative is to hold a second, typed reference to the same instance: ```kotlin val repo = mockk<Repository>(moreInterfaces = arrayOf(AutoCloseable::class)) val closeable = repo as AutoCloseable verify { closeable.close() } ``` Both refer to the same mock; MockK's recording is per instance, so calls made through either reference land in the same record. Note the practical detail in the snippet above: `close()` returns `Unit`, and on a strict mock an unstubbed call throws. Either stub it (`just Runs`) or create the mock with `relaxUnitFun = true` — which is why the two creation options are so often seen together. ## Can it be used for classes? The parameter is named `moreInterfaces` for a reason: it is for **interfaces**. The mock's primary type is the reified `T`; you cannot make one mock be two unrelated concrete classes, because a JVM object has one class. If production code checks `is SomeFinalClass`, `moreInterfaces` is not the tool — you need a mock of that class, or a design change. ## Judgement Reaching for `moreInterfaces` is a small design signal. Runtime type tests on a collaborator are a form of hidden contract: the declared parameter type does not tell you the object may also need to be closeable. If the code is yours, expressing the capability directly (a dedicated parameter, a wrapper that owns the lifecycle, or a richer interface) usually beats teaching every test about the hidden branch. `moreInterfaces` earns its place when the branching code is a framework, a third-party library, or legacy you are not refactoring today — exactly where a mock is the only way to hit the branch at all. ## Interview framing Say: *one mock instance, wider runtime type, so `is`/`as` checks in production code pass; cast inside `every`/`verify` to reach those members; pair with `relaxUnitFun` for `Unit`-returning lifecycle methods; and note that needing it often points at a runtime type test you might rather remove.*

  • Can `moreInterfaces` be used to make one mock stand in for two unrelated concrete classes?
    No. A JVM object has exactly one class, and the mock's class is the reified type you asked for; the parameter only widens the set of implemented interfaces. If production code does `is SomeConcreteClass`, you need a mock of that class instead, or a design change that expresses the capability as an interface.
  • You added `moreInterfaces = arrayOf(AutoCloseable::class)` and the test now fails with "no answer found for: … close()". What happened?
    The extra interface's members are ordinary mock members, so on a strict mock an unstubbed call still throws — the widened type made the branch reachable, and now the call needs an answer. Either stub it (`every { (mock as AutoCloseable).close() } just Runs`) or create the mock with `relaxUnitFun = true` so `Unit`-returning members answer automatically.

saying these in an interview costs you the question

  • "It creates a second mock for the extra interface" — it is one instance with a wider runtime type.
  • "You can list classes there too, to mock two class types at once" — a JVM object has one class; the parameter is for interfaces.
  • "Verification needs a separate mock for the extra interface's calls" — calls through either reference land on the same instance's record.
  • "You can call the extra interface's members without a cast" — the compiler still sees only the reified type.
  • Treating a passing `is` check as proof the branch is tested without also verifying the call inside it.

context