Files
werkator/docs/prs/2026-10-10-PR#136-mockk-mocks-cleared-after-every-spec.md
claude-code 0bda344407
werkator build succeeded after 02:43
werkator/deploy build succeeded after 00:09
MockK mocks cleared after every spec (v1.38.4) (#136)
Co-authored-by: claude-code <claude-code@javagil.de>
2026-10-10 10:03:31 +02:00

2.8 KiB

PR#136: MockK Mocks Cleared after Every Spec

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

The Problem

The nightly mutation build of 2026-10-10 passed the Mutation-CRAP limit of 15 for the first time, but still went red. One mutant in Watcher.pruneWorktrees ended in MEMORY_ERROR, so werkcrappit found its method INCONCLUSIVE and refused the gate.

The mutant itself is harmless: applied by hand, WatcherUnitTest kills it with two failing tests. A local PIT run of Watcher alone, with the heap of a minion on mih09 (1484 MiB, a quarter of its RAM), ended the same mutant in MEMORY_ERROR. With 3 GiB of heap, PIT killed it.

PIT runs all 190 mutants of Watcher in one minion, and for each mutant it runs the whole spec again. The live heap of that minion grew by about 200 MB every 15 seconds, also right after a full GC. After 70 seconds, a class histogram showed 123147 live MockKStub objects: every mock the minion had ever created.

MockK holds its stubs only weakly, but the answers and recorded calls of a stub point back to its mock. So the weak registries never let a mock go. A normal test run is short enough not to notice, but a minion runs every spec a few hundred times.

Non-Goals

  • Finding out why MockK still keeps the emptied stubs. An emptied stub is small: with the fix, the heap of the minion grows four times slower, and no mutant ends in MEMORY_ERROR.

The Scenarios

Scenario#136.01: A spec clears its mocks when it ends

  • Given a spec with a mock that has an answer and a recorded call
  • When the spec ends
  • Then the mock has neither
  • So that old mocks do not fill the heap of a PIT minion.
Verified by

The Solution

  • The Kotest project configuration registers ReleaseMocksAfterSpec, which calls clearAllMocks() after every spec.
  • After a spec, not after a test: a spec may stub its mocks once for all its tests. No spec stubs a mock in a bean definition, so a Spring context cached for the next spec loses nothing it needs.

The same local PIT run of Watcher, with 1484 MiB and the fix:

Run Live heap growth (after full GC) Mutants in MEMORY_ERROR
Without the fix about 200 MB per 15 s 1
With clearAllMocks() about 45 MB per 15 s 0

Follow-up PRs

  • A heap limit for the minions that fits, measured with a full run now that the mocks are released (see PR#135).