Build1 publisher3 min readPublished
Sample coverage config lets one of the four CI gates pass without new coverage
A dev.to how-to proposes four CI gates for LLM-written tests: execution, coverage, mutation, drift. Read the sample configs line by line and two of them would let through a test that asserts nothing.
The Engineer · Build desk
What happened
- A dev.to post argues AI-generated tests should be treated as untrusted input and forced through four CI gates: execution, coverage, behavior via mutation testing, and drift.
- The execution gate is an ordinary GitHub Actions workflow on push and pull_request that checks out the repository, runs npm ci, and then runs the Jest suite in CI mode.
- The combined pipeline runs execution, coverage and mutation as three parallel jobs on every pull request, each with its own checkout and dependency install, and merges only when all pass.
- The post names mutation testing as the strongest guard against hallucinated assertions, on the grounds that a test surviving a mutation is not validating behavior.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost Every pull request pays for three checkouts, three dependency installs, a coverage run and a full mutation run before it can merge, and that bill lands on CI minutes for each push.
- constraint A project-wide coverage floor answers a question about the repository, not about the diff, so any team already above the floor gets no signal from that gate on an individual generated test.
- decision Anyone copying the mutation job has to choose its break threshold themselves, because leaving the sample value in place makes the strongest of the four gates advisory.
- contradiction A team told that gates replace manual review for correctness still needs a human to sign every snapshot diff, so the drift gate puts back the review step the other gates were meant to remove.
Coverage and assertion are different measurements, and the post makes that distinction itself before its config blurs it. In the coverage step it warns that AI can generate tests which execute code without asserting meaningful behavior [7]. The filter it then prescribes for that failure is a Jest coverageThreshold of 80 for branches, functions, lines and statements [5], on the reasoning that a test which does not meaningfully increase coverage is probably asserting on trivial or hallucinated paths [6]. A test that calls a function and checks nothing still records every line it touched. The threshold is also global. A repository sitting at 85 percent statements clears an 80 percent floor even when a new generated test covers nothing new [15].
The gate that can see an empty assertion is the mutation gate, and the post is right to call mutation testing the strongest guard against hallucinated assertions [8]. Its example .strykerrc.json sets thresholdHigh to 80, thresholdLow to 60 and thresholdBreak to 0 [9]. The pipeline then runs that job as `npx stryker run` and says a PR merges only when all gates pass [10]. Whether the job can fail a build at all is a question about the break threshold, and the sample value is 0. Read Stryker's documentation on that field before copying the file.
The drift gate is a snapshot. The example calls userService.getActiveUsers, matches the result with toMatchSnapshot, and fails CI on any change until a developer explicitly approves the diff [13]. A snapshot records whatever the code did on the day it was taken. If the generated test captured a bug, the approved snapshot then defends the bug. "Review generated code, but don't trust it. Gates replace manual review for correctness," the post says [14].
Its tip list also says to fail fast, running execution and coverage before mutation to save time [11]. The pipeline it prints runs execution, coverage and mutation as three parallel jobs, each checking out the repository and installing dependencies before it starts [12][10].
None of this argues against the design. Treating generated tests as untrusted input is the right default in a repo where the code arrives faster than anyone can review it [1], and the execution gate costs a checkout, an install and one npm test [3]. "If a test doesn't run, it provides zero protection," the post says [4]. It does not report results from running the gates on a real codebase, so there is no hallucination rate in it to size your own suite against [17]. What transfers is the pipeline shape. The coverage scope and the break threshold are set by whoever copies it, and in the samples they are global and 0 [5][9].
What to watch
- Published mutation scores from before and after gating AI-written tests on a real repository would move this design from recipe to evidence.
- An updated sample .strykerrc.json with a non-zero break threshold would make the behavior gate an actual merge blocker.
- Reported wall-clock cost of a mutation run on a large suite would tell teams whether the behavior gate can sit on every pull request.