Unit Test Generator for Claude Code

Generate a focused unit test suite for a function or module: happy path, edge cases, error paths and one property-style test, in the project's existing test framework, without touching production code.

by Nobodylovesyou ▲ 0 📋 0 copies 👁 1

Fill in the variables

Prompt


            

Example input

Target: src/utils/slugify.ts
```ts
export function slugify(input: string, maxLength = 60): string {
  const base = input
    .normalize("NFKD")
    .replace(/[̀-ͯ]/g, "")
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");
  if (base.length <= maxLength) return base;
  const cut = base.slice(0, maxLength);
  const lastDash = cut.lastIndexOf("-");
  return lastDash > 0 ? cut.slice(0, lastDash) : cut;
}
```
Stack: TypeScript 5 / Node 22 / Vitest
Conventions: tests next to source as *.test.ts, describe/it, expect
Spec: Produce a URL slug: lowercase ASCII letters, digits and single dashes; no leading or trailing dashes; accents stripped; truncated to maxLength without cutting a word when possible.
Concerns: Unicode input, very long input, inputs that are all symbols, maxLength smaller than the first word.

Example output generated with Claude Sonnet 4.6 from the example input above

Checklist - Basic conversion: spaces and punctuation become single dashes, lowercase. - Accents are stripped (NFKD + combining-mark removal). - Non-Latin scripts that have no ASCII mapping collapse to dashes and are trimmed; all-symbol input yields an empty string. - Leading/trailing dashes are removed. - Length at or under maxLength is returned unchanged. - Over maxLength: cut at the last dash within the limit. - Over maxLength with no dash in the window (or first word longer than maxLength): hard cut at maxLength. - maxLength default of 60 is applied. - Property: output always matches /^[a-z0-9]+(-[a-z0-9]+)*$/ or is empty, and length ≤ maxLength. No untestable dependencies: the function is pure. ```ts // src/utils/slugify.test.ts import { describe, it, expect } from "vitest"; import { slugify } from "./slugify"; describe("slugify", () => { it("lowercases and replaces runs of punctuation and spaces with a single dash", () => { expect(slugify("Hello, World! Again")).toBe("hello-world-again"); }); it("strips accents and diacritics", () => { expect(slugify("Crème Brûlée à la Noël")).toBe("creme-brulee-a-la-noel"); }); it("removes leading and trailing dashes", () => { expect(slugify(" --Hello world-- ")).toBe("hello-world"); }); it("returns an empty string for input with no ASCII letters or digits", () => { expect(slugify("!!! ??? ...")).toBe(""); expect(slugify("日本語")).toBe(""); }); it("returns the slug unchanged when it is within maxLength", () => { expect(slugify("short title", 20)).toBe("short-title"); }); it("truncates at the last word boundary within maxLength", () => { expect(slugify("the quick brown fox jumps", 15)).toBe("the-quick-brown"); }); it("hard-cuts when the first word is longer than maxLength", () => { expect(slugify("supercalifragilistic expialidocious", 8)).toBe("supercal"); }); it("applies the default maxLength of 60", () => { const long = Array.from({ length: 30 }, (_, i) => `word${i}`).join(" "); const out = slugify(long); expect(out.length).toBeLessThanOrEqual(60); expect(out.endsWith("-")).toBe(false); }); it.each([ ["Ünïcödé Tïtlé", 60], ["a".repeat(200), 10], ["mix3d CASE and 42 numbers", 12], ["--- dashes --- everywhere ---", 60], ])("always produces a valid slug within maxLength for %j", (input, max) => { const out = slugify(input, max); expect(out.length).toBeLessThanOrEqual(max); expect(out === "" || /^[a-z0-9]+(-[a-z0-9]+)*$/.test(out)).toBe(true); }); }); ``` Run result: 12 tests, 12 passed (vitest run src/utils/slugify.test.ts). No production changes. Not covered - Behaviour for maxLength ≤ 0 is undefined in the spec; `slice(0, 0)` returns "" today. Add a test once the intended behaviour is decided. - Right-to-left scripts with ASCII digits (e.g. Arabic text with numbers) collapse to the digits only; this follows from the regex and is documented by the empty-string test, but may not be the desired product behaviour.

Why this works & how to customize

Why it works: the checklist step makes the model enumerate branches before writing, which is where coverage comes from; the "one assertion, no conditionals" rule prevents tests that always pass; and the ban on editing production code keeps the agent from "fixing" behaviour to match its own assumptions. The property test catches the class of bugs individual examples miss.

How to customize: in Claude Code, give the file path instead of pasting code and the agent reads it, writes the test file next to the source and runs it. For code with time or network dependencies, expect the agent to stop at step 2 and propose a seam; accept it or tell it to use the project's existing mocking approach. For Python, say "pytest with parametrize" and the property test becomes a parametrized sweep (or hypothesis, if the project already uses it). Keep the concerns field honest; it is where the valuable edge cases come from.
Open in Claude Code → 🍴 Fork

Comments (0)

Sign in to comment.

No comments yet.