Skip to content

Extract shared test helpers to remove cross-file duplication#3

Merged
vasylenko merged 2 commits into
mainfrom
chore/dedupe-test-helpers
May 13, 2026
Merged

Extract shared test helpers to remove cross-file duplication#3
vasylenko merged 2 commits into
mainfrom
chore/dedupe-test-helpers

Conversation

@vasylenko
Copy link
Copy Markdown
Owner

What

Follow-up to #2 that addresses the Sonar quality gate finding: 3.3% duplication on new code, over the 3% threshold. The duplication is pre-existing — startMock, textOf, and HAPPY_FIXTURE were copy-pasted across tests/cli.test.ts, tests/server.test.ts, and tests/e2e.test.ts. PR #2's renames happened to touch lines inside those duplicated blocks, which is what surfaced the finding.

Extract the truly identical pieces to a new tests/_helpers.ts:

  • startMock — byte-for-byte identical in all three files (~20 lines × 3 copies).
  • textOf — identical in server.test.ts and e2e.test.ts.
  • HAPPY_FIXTURE — server's 3-section version. cli.test.ts's assertions are a subset of what this fixture exposes, so they continue to pass against it.

Intentionally kept per-file:

  • spawnClient / spawnBuilt / runCli — these differ meaningfully in transport and command. A polymorphic factory would muddy test-local clarity for ~10-line functions.
  • e2e.test.ts's own HAPPY_FIXTURE — uses distinctive sentinel strings ("E2E Fixture Heading") to prove the e2e tests are exercising their own pipeline, not accidentally hitting the server-test setup.

The new file is named _helpers.ts (underscore prefix). The runner pattern tsx --test tests/*.test.ts in package.json matches only *.test.ts and skips it by name — no runner change required.

Net diff: +62 / -125 across 4 files. No behavior change.

Verification

  • npm run build clean.
  • npm test: 50/50 pass on macOS.

vasylenko added 2 commits May 13, 2026 08:39
Sonar flagged 3.3% duplication on new code in PR #2 (limit 3%). The
duplication is real and pre-existing: `startMock`, `textOf`, and
HAPPY_FIXTURE were copy-pasted across cli.test.ts, server.test.ts, and
e2e.test.ts. PR #2's renames happened to touch lines inside the
duplicated blocks, which surfaced the finding.

Extract the truly identical pieces to `tests/_helpers.ts`:
- `startMock` — byte-for-byte identical in all three files (20 lines × 3
  copies).
- `textOf` — identical in server.test.ts and e2e.test.ts.
- `HAPPY_FIXTURE` — server's 3-section version. cli.test.ts's assertions
  are a subset of what this fixture exposes, so they continue to pass.

Keep per-file:
- `spawnClient` / `spawnBuilt` / `runCli` — these differ meaningfully in
  transport and command. A polymorphic factory would muddy test-local
  clarity for ~10-line functions.
- e2e.test.ts's own HAPPY_FIXTURE — uses distinctive sentinel strings
  ("E2E Fixture Heading") to prove the e2e tests are exercising their
  own pipeline, not accidentally hitting the server-test setup.

The new file uses `_helpers.ts` (underscore prefix). The runner pattern
`tsx --test tests/*.test.ts` (package.json) matches only `*.test.ts` and
skips it by name.

Net diff in test files: +3 import lines, -125 duplicated lines. The new
helpers file is ~63 lines. No behavior change; all 50 tests pass.
Follow-up to the first commit on this branch. Three additional extractions
that the initial pass didn't cover; together they push the PR's net LOC
delta from -63 to -101 vs main.

New helpers in tests/_helpers.ts:
- `spawnClient({ name?, env? })` — moved from server.test.ts and
  parameterized so snapshots.test.ts can share it. Existing callers using
  env pass `{ env: {...} }` now.
- `assertSchemaRejection(client, args, msg)` — captures the F27 pattern
  (try/catch around callTool to handle SDK-version-variant rejection
  shapes, then assert no `[code]` prefix leaked). Used at 3 sites
  (zod-syntax test, savePath relative, savePath tilde).
- `spawnAndCaptureExit(args, env)` — captures the env-var-validation
  pattern (spawn → collect stderr → await exit). Used at 3 sites.
- `ERROR_CODE_PREFIX_RE` — single source for the seven `[code]` prefixes
  the regex was previously inlined at 3 sites.

snapshots.test.ts: the inline `createServer` plumbing and
`StdioClientTransport` setup are replaced by `startMock` (with a closure
over `currentHtml`) and `spawnClient({ name: "snapshot-test" })`. The
`Mock` type and `setHtml` wrapper are gone — tests mutate `currentHtml`
directly, which the mock handler reads on each request.

Avoided one Sonar finding while at it: `opts.env ?? {}` was flagged by
S7744 ("empty object is useless"). Object spread already handles
`undefined`, so the `?? {}` fallback was strictly redundant — replaced
with `...opts?.env`.

No behavior change; all 50 tests pass.
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
4.3% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces test-code duplication by extracting shared utilities (mock HTTP server setup, MCP client spawning, common fixtures, and repeated assertion helpers) into a dedicated tests/_helpers.ts module, addressing the Sonar duplication finding without changing runtime behavior.

Changes:

  • Added tests/_helpers.ts containing startMock, spawnClient, textOf, HAPPY_FIXTURE, plus shared assertion/subprocess helpers.
  • Updated cli.test.ts, server.test.ts, e2e.test.ts, and snapshots.test.ts to import and use the shared helpers.
  • Simplified repeated schema-boundary rejection assertions and env-var startup-failure subprocess plumbing in server.test.ts.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/_helpers.ts New shared test helper module consolidating duplicated mock/client/fixture/assertion logic.
tests/snapshots.test.ts Switched to shared startMock and spawnClient; uses a shared mutable HTML variable for responses.
tests/server.test.ts Replaced duplicated helpers with imports; refactored schema-rejection and subprocess checks to shared helpers.
tests/e2e.test.ts Uses shared startMock and textOf while keeping e2e-specific fixture and build-spawn logic.
tests/cli.test.ts Uses shared startMock and shared HAPPY_FIXTURE while keeping CLI process harness local.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/_helpers.ts
Comment on lines +114 to +116
const exitCode = await new Promise<number>((resolve) =>
child.on("exit", (code) => resolve(code ?? -1)),
);
@vasylenko vasylenko merged commit dfdc3f9 into main May 13, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants