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

58 lines
2.8 KiB
Markdown

# 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
- [ReleaseMocksAfterSpecUnitTest](../../src/test/kotlin/de/hoennig/werkator/framework/ReleaseMocksAfterSpecUnitTest.kt)
## 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).