
Writing tests is the task every developer puts off. You ship the feature, tell yourself you'll add tests "later," and then a bug lands in production and you spend three hours debugging something a two-line test would have caught in thirty seconds.
Claude Code takes that entire workflow off your plate — not just the boilerplate, but the edge cases too. This tutorial walks through exactly how to do it, what to expect, and where the approach breaks down.
The Problem: Test Coverage Is Always "Tomorrow's Job"
Every senior dev has a codebase in their past where the test coverage badge reads 0%. Not because nobody knew how to write tests — because writing them takes real time, and that time always competes with shipping.
The situation compounds with legacy code. If you inherit a utils/ folder full of undocumented helpers, you genuinely can't refactor safely without tests. But writing tests for code you didn't write, against behavior that was never specced, is painful enough that most teams just leave it alone.
That stalemate is exactly where Claude Code is useful.
The loop is real. The exit is automation.
The Fix: One Command, One File, Done
Open your terminal in the project root with Claude Code running. Point it at the file you want covered.
claude "Write Jest tests for every function in this file. Cover the happy path and all error cases."
What I saw on a 50-line utility module: a complete calculator.test.js appeared in under 30 seconds. Around 95% of the generated tests passed without any edits. The remaining 5% needed small adjustments — usually a wrong assumed return type or a mock that didn't match the actual module shape.
For a file I would have spent 30 minutes testing manually, that's a 60x speedup on the first pass.
Here's the baseline command pattern I use:
claude "Analyze utils/calculator.js and generate a new test file calculator.test.js. \
Use Jest. Include normal cases, boundary values, null inputs, and exception paths."
Claude reads the function signatures, infers intent from variable names and return values, and constructs test cases from that. It doesn't need comments or docs — though they help.
Section 3: Edge Cases Are Where This Shines
The real value isn't the happy path. Any dev can write expect(add(2, 3)).toBe(5) in thirty seconds. What takes time is thinking through every way the function can break.
Claude catches the cases that human reviewers skip because they're thinking about the feature, not the failure modes:
| Input type | Example | What Claude generates |
|---|---|---|
| Null / undefined | add(null, 3) |
Expects throw or graceful fallback |
| Empty array | sum([]) |
Expects 0 or specific error |
| Negative numbers | divide(-10, -2) |
Verifies sign handling |
| Zero denominator | divide(5, 0) |
Expects Infinity or throw |
| Oversized input | factorial(1000) |
Checks for stack overflow handling |
| Type coercion | add("2", 3) |
Catches implicit "23" bugs |
To push Claude specifically into edge-case mode:
claude "Strengthen the existing tests with edge cases. \
Make sure you cover: null, undefined, empty arrays, negative numbers, \
and boundary values like 0 and Number.MAX_SAFE_INTEGER."
What I got back was a set of tests that immediately caught two real bugs I didn't know existed — one involving a silent NaN return and one where an empty input returned undefined instead of throwing.
Attaching Tests to Legacy Code
This is where the workflow pays off the most. If you have existing code without any tests — the kind of file nobody wants to touch because refactoring it means flying blind — Claude Code gives you a way in.
claude "Analyze utils/legacyParser.js and create a new file \
utils/legacyParser.test.js. Infer the expected behavior from \
the existing implementation."
The key phrase is "infer the expected behavior from the existing implementation." You're telling Claude to treat the current behavior as the spec, not some ideal behavior. That's what you actually want when you're trying to freeze behavior before a refactor.
Once the tests exist, you can refactor with confidence. The fear of breaking something invisible disappears because now you'll know immediately if you do.
A few gotchas I hit when applying this to legacy code:
Tightly coupled functions — if your utility calls three other utilities internally, Claude may generate tests that hit those dependencies too. Add --mock intent to your prompt: "mock all external module calls so tests are isolated."
Implicit globals — some older JS files rely on window, process.env, or similar. Claude will usually mock these, but double-check the generated setup blocks.
Environment differences — if you run on both Mac and Linux (or inside Docker), watch for path.sep differences in any test that touches the filesystem. Add os.path.join or Node's path.join explicitly if Claude hardcodes /.
# Mac/Linux local run
npm test
# Inside Docker — same command, but path assertions may differ
docker exec my-container npm test
Verification: What "Done" Actually Looks Like
After Claude generates the file, run your test runner immediately:
npx jest calculator.test.js --verbose
Expected output when things go well:
PASS utils/calculator.test.js
✓ add returns correct sum (3 ms)
✓ add handles null input (1 ms)
✓ divide throws on zero denominator (2 ms)
✓ multiply handles negative numbers (1 ms)
✓ sum returns 0 for empty array (1 ms)
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
If some tests fail on first run, check whether the failure is in the test or in the source. In my experience, about half the failures I saw were Claude correctly identifying a bug in my source code — the test was right, the implementation was wrong.
Closing
"I don't know how to write tests" is no longer a valid excuse for skipping coverage. Point Claude at a file, give it one sentence of instruction, and get a working test suite in under a minute.
The bigger shift is psychological: once you have tests, you stop flinching before every refactor. That's not a small thing. Untested code accumulates because nobody wants to touch it — and nobody wants to touch it because it's untested. Tests break that cycle.
Next step worth trying: wire the test generation into your pre-commit hook so any new utility file automatically gets Claude to scaffold its test file before the commit goes through.
🐦 Faster updates on X: @baegseungh7061
📚 More in this series: Code Intro
💌 Subscribe: Follow on X or grab the RSS
댓글
댓글 쓰기