MockK's verify accepts a timeout parameter, as in verify(timeout = 500) { listener.onDone() }. What does MockK actually do during those 500 milliseconds, and how is that different from calling Thread.sleep(500) before a plain verify?
answer
- timeout = milliseconds on verify/coVerify
- retries the block, returns on first success
- upper bound, not a fixed cost
- sleep = one check at a guessed instant
- verifyOrder/verifySequence have no timeout
basics
~20 sMockK re-evaluates the verification block against the mock's recorded calls until it passes or the deadline expires, returning the instant it succeeds. Thread.sleep always burns the full delay and then checks once, at a single fixed moment.
solid answer
~50 s`timeout` is a milliseconds value on MockK's `verify` (and `coVerify`); the default 0 means no waiting. With a timeout, MockK blocks the calling thread and repeatedly re-runs the verification block against the calls recorded on the mock so far. The first time the block is satisfied it returns; if the deadline passes it throws the same verification-failure assertion you'd get without a timeout. The practical difference from `Thread.sleep` is cost and correctness. A sleep is a fixed guess: too short and the test flakes, too long and every run pays for it, and after sleeping you still assert exactly once, so a slightly late call still fails. A timeout is an *upper bound* — a passing test typically returns in a few milliseconds, so you can set it generously (seconds) and only failing tests pay the full price.
code
kotlin · 7 linesval listener = mockk<UploadListener>(relaxed = true)
val uploader = Uploader(listener, Executors.newSingleThreadExecutor())
uploader.upload(file) // returns immediately; work continues on another thread
// returns as soon as the call is recorded; fails after 2s if it never is
verify(timeout = 2000) { listener.onFinished(file) }go deeper
Know that async code needs a waiting verify and that the parameter is milliseconds; be able to write verify(timeout = 1000) { ... } instead of a sleep.
Explain the retry-until-satisfied loop and the early return, and argue why an upper bound removes the flaky/slow tradeoff a sleep forces on you.
Add the operational reasoning: how to pick values that survive loaded CI, what the failure message does and does not tell you, and that only the verified call is known to have completed.
Frame it as a suite-level policy — timeouts are bounded waits at edges you do not control, and a codebase that needs many of them usually lacks an injectable seam for its concurrency.
## The problem it solves In a synchronous test the collaborator is called before your test method resumes, so `verify { mock.f() }` is safe the moment the call under test returns. As soon as the production code hands work to another thread, an executor, or a coroutine on a real dispatcher, the call under test returns *before* the collaborator has been touched. A plain `verify` then fails not because the code is wrong but because the test asked too early. ## What the parameter is MockK's `verify` has a `timeout: Long` parameter measured in milliseconds, defaulting to `0` (no waiting). `coVerify` takes the same parameter for suspend functions. You use it exactly like a normal verify: ``` verify(timeout = 2000) { listener.onFinished(file) } ``` ## The mechanics MockK does not change the mock's behaviour, and it does not re-run your test. It blocks the thread that called `verify` and evaluates the verification block repeatedly against the list of calls recorded on that mock, retrying while the verification is unsatisfied. Two consequences follow directly: 1. **It returns early.** The moment the recorded calls satisfy the block, the loop stops and `verify` returns. A test that would have passed after 3 ms costs about 3 ms even if you wrote `timeout = 5000`. 2. **The failure is the ordinary failure.** When the deadline expires, MockK throws the usual verification-failure assertion showing the expected call and the calls it actually recorded — the same diagnostics as a non-timeout verify, so nothing is lost by adding a timeout. Calls made on *any* thread are recorded against the same mock instance, which is why polling from the test thread eventually sees work performed by a background thread. The mock is shared state; the timeout is just a bounded wait on that shared state changing. ## Why this beats sleeping `Thread.sleep(n)` before a verify has three separate problems. First, the value is a guess about the slowest machine that will ever run the suite — CI under load is far slower than a developer laptop, so the value that passes locally flakes in the pipeline. Second, the cost is unconditional: a hundred tests with a 500 ms sleep add fifty seconds to *every* green run. Third, and most subtly, sleeping does not remove the race; it only moves it. You still assert exactly once, at one instant, so a call arriving one millisecond after the sleep still fails the test. A timeout inverts all three. It is an upper bound rather than a fixed cost, so you can be generous; it converges as soon as the condition is true rather than at a guessed instant; and only genuinely broken tests pay the full duration. ## What it does not do * It does not wait for *quiet* — it waits for a condition to become true, so it cannot prove something never happens (a verification that is already satisfied returns immediately). * It does not synchronise anything after the block. Other in-flight work may still be running when `verify` returns; only what you verified is known to have happened. * The `verifyOrder`, `verifySequence` and `verifyAll` shorthands take no timeout parameter. `verify` itself exposes both `ordering` and `timeout`, so ordered verification with waiting goes through `verify`. * It cannot make exceptions thrown on the background thread visible. If the worker thread dies, nothing propagates into the test; the timeout expiry is your only signal, and the message will say "the call never happened" rather than "the worker crashed". ## Choosing a value Because success returns early, treat the number as a *deadline you are willing to wait before declaring failure*, not as an expected duration. Seconds are normal for work crossing a thread boundary. Very small values (a few tens of milliseconds) reintroduce exactly the flakiness you were trying to remove, because thread scheduling under a loaded CI agent is not bounded by anything that small.
- Does a large timeout slow down a passing test?No. MockK stops retrying the moment the verification is satisfied, so a green test costs roughly the real latency of the async work. The timeout only bounds how long a failing test waits before it reports the failure. That is why generous values are safe and small guessed values are not.
- What does the failure look like when the timeout expires?You get the standard MockK verification-failure assertion: the expected call as written in the block, plus the calls that were actually recorded on the mock. It does not report anything about threads. If the background work crashed, you will see "no matching calls" rather than the underlying exception, so worth logging or capturing failures in the worker.
Thread.sleep is setting an alarm and looking exactly once when it rings. A timeout verify is glancing at the door repeatedly until your guest arrives, giving up only after the agreed hour.
saying these in an interview costs you the question
- Thinking the test always waits the full timeout, so keeping the value tiny "for speed"
- Believing timeout retries the test body or re-invokes the code under test
- Assuming a plain Thread.sleep is equivalent because "it waits too"
- Expecting an exception thrown on the background thread to surface as the test failure
- Trying to pass timeout to verifyOrder or verifySequence