← Blog

Claude API Unit Test Generator: Build Example

2026-10-03 · 5 min read · SubToAPI Team

Generating unit tests by hand is repetitive work that eats into time better spent on actual logic. The Claude API is well suited to this because it can read a function, infer its contract, and produce test cases covering typical inputs, edge cases, and error conditions — consistently, in seconds.

This article walks through a complete, working example of a Claude-based unit test generator: the prompt structure, the API call, parsing the output into a runnable test file, and handling multiple languages/frameworks (Jest, pytest, JUnit). You'll end up with a script you can run against any source file to get a draft test suite.

Why use an LLM for test generation

Static analysis tools can generate boilerplate test stubs, but they don't understand intent. Claude can read a function's name, parameters, docstring, and body, and reason about what it's supposed to do — which means it can write assertions that actually check behavior, not just "does it run without throwing."

Typical use cases:

This is not a replacement for human-reviewed tests on critical logic, but it's a strong starting point and a fast way to catch obvious gaps.

Designing the prompt

The quality of generated tests depends heavily on prompt structure. A loose "write tests for this" prompt produces shallow output. Be explicit about:

  1. The testing framework (Jest, pytest, JUnit 5, etc.)
  2. What to cover — happy path, edge cases, error handling
  3. Output format — ask for only code, no explanation, so you can parse it directly
  4. Context — paste the function plus any type signatures or imports it depends on

Here's a prompt template that works well in practice:

You are generating unit tests for the following JavaScript function.
Use Jest. Cover:
- the normal/expected use case
- at least two edge cases (empty input, boundary values)
- one error-handling case if the function can throw

Return ONLY the test code, no explanation, no markdown fences.

Function:
---
function calculateDiscount(price, percentage) {
  if (percentage < 0 || percentage > 100) {
    throw new Error("Invalid percentage");
  }
  return price - (price * percentage / 100);
}
---

Calling the API

Here's the generator script using Claude's Messages API through SubToAPI, which exposes the same endpoint shape with a sub_live_... key:

const fs = require("fs");

async function generateTests(sourceCode, framework = "Jest") {
  const response = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      model: "claude-sonnet-4-5",
      max_tokens: 1024,
      messages: [
        {
          role: "user",
          content: `You are generating unit tests for the following function.
Use ${framework}. Cover the normal case, at least two edge cases, and one
error-handling case if applicable. Return ONLY the test code, no explanation.

Function:
---
${sourceCode}
---`
        }
      ]
    })
  });

  const data = await response.json();
  return data.content[0].text;
}

async function main() {
  const source = fs.readFileSync("./src/calculateDiscount.js", "utf8");
  const tests = await generateTests(source, "Jest");
  fs.writeFileSync("./tests/calculateDiscount.test.js", tests);
  console.log("Test file written.");
}

main();

This takes a source file, sends it to Claude with the framework and coverage instructions baked into the prompt, and writes the response straight to a .test.js file. If you're new to the Messages API shape, the quickstart and messages docs cover the request/response format in detail.

Handling multiple languages and frameworks

A generator that only handles one language is limiting. Parameterize the framework and language, and adjust instructions accordingly:

const frameworkInstructions = {
  jest: "Use Jest syntax with describe/it blocks.",
  pytest: "Use pytest with plain assert statements and fixtures where useful.",
  junit: "Use JUnit 5 with @Test annotations and assertEquals/assertThrows."
};

function buildPrompt(sourceCode, framework) {
  return `You are generating unit tests for the following code.
${frameworkInstructions[framework]}
Cover the normal case, edge cases, and error handling.
Return ONLY the test code, no explanation.

Code:
---
${sourceCode}
---`;
}

Swap this into the generateTests function above and you have one script that handles JavaScript, Python, and Java source files by changing a single parameter.

Parsing and validating output

Even with "return only code" instructions, models occasionally wrap output in markdown fences. Strip them defensively before writing to disk:

function cleanCodeBlock(text) {
  return text
    .replace(/^```[\w]*\n/, "")
    .replace(/\n```$/, "")
    .trim();
}

After writing the file, run the actual test suite (jest, pytest, etc.) as a validation step. If the generated tests fail to even parse or run, that's a signal to tighten the prompt — for example, explicitly listing the import path or exported function name, since Claude sometimes guesses incorrectly if the source snippet is taken out of context.

Batching across a codebase

For generating tests across many files, loop over a directory and call the API per file, with a short delay or concurrency limit to avoid hitting rate limits:

const files = fs.readdirSync("./src").filter(f => f.endsWith(".js"));

for (const file of files) {
  const source = fs.readFileSync(`./src/${file}`, "utf8");
  const tests = await generateTests(source, "jest");
  fs.writeFileSync(`./tests/${file.replace(".js", ".test.js")}`, cleanCodeBlock(tests));
  console.log(`Generated tests for ${file}`);
}

This pattern — one script, looped across a repo — is a realistic way to bring an untested legacy codebase up to a baseline coverage level overnight.

Running this behind SubToAPI

If you're already using Claude through a personal or team subscription, SubToAPI turns that access into a standard HTTPS API with application keys instead of session tokens, so scripts like the one above can run unattended in CI or on a schedule. It also gives you per-key usage metadata, which is useful for tracking how much a test-generation batch job costs versus day-to-day development usage. Plans start at €9/month on the Solo tier, with team seats on the Team (€19/seat) and Scale (€49/seat) plans — see pricing or start with a free trial at signup.

FAQ

Does Claude-generated test code need manual review? Yes. Treat it as a strong first draft — check that assertions match actual intended behavior, not just whatever the function currently does, especially for edge cases and error paths.

Which Claude model is best for test generation? Claude Sonnet models are a good balance of quality and speed for this task; for very large or complex functions, a more capable model may catch more edge cases at the cost of latency.

Can this approach generate integration tests, not just unit tests? Yes, with prompt changes — provide more context (multiple functions, API contracts, or a description of the system under test) and explicitly ask for integration-level scenarios instead of isolated function behavior.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →