Unit Test Generator

Paste one function and read back the cases it needs before you write an assertion. Guard clauses, the numbers your comparisons are pinned to, and the branches with no early return all come out of the code you pasted, so the plan matches the function in front of you instead of a template.

Unit test planning bench

How far to push

Waiting for a function

Paste a function on the left. The plan builds itself as you type.
0Parameters
0Branch points
0Decision points
0Cases planned
0%Branch reach
  • Reads your guards
  • Exports a CSV plan
  • Runs in your tab

Turning one function into a test plan a reviewer will sign off

Most test generators hand back a describe block with three assertions passing no matter what the function does. An assertion checking a result is defined, on a function returning a boolean, is a green tick and nothing else. It survives a rewrite of the whole body. The bench above reads the body first, then writes cases pointed at what the body actually does: the guards, the numbers your comparisons are pinned to, the branch with no early return, the call reaching outside the function. Every row in the plan names the line of code behind it. Every row the parser could not settle says so in plain words.

What the analyzer reads before writing a single test

Four passes run over the pasted code, and each one feeds a different part of the plan.

Where each category of case comes from

CategoryRead fromThe defect it catches
Happy pathParameter types, with values chosen to clear every guardA signature change nobody propagated
Type guardA typeof, is_numeric, isinstance or instanceof checkA caller passing the string "12" where a number was meant
Null inputA null, None or falsy check with an early exitAn optional field arriving absent from an upstream service
Empty inputA length, count, empty or isEmpty checkThe zero-row result set nobody tried in staging
BoundaryA comparison against a literal numberThe off-by-one at the edge of a range
Equivalence classAn equality check against a literal stringA plan, role or status name spelled differently in one place
BranchA condition with no early returnA path exercised by no test at all
NegativeParameters with no guard covering themThe contract you never wrote down

The last row is the one worth arguing about in review. A negative case appears when nothing in the function rejects a wrong type or a missing argument for a given parameter. The tool has no opinion on whether the answer should be a thrown error, a coerced value or a fallback. What it does is put the question in front of you, in a row with a name, before the choice gets made by accident in production.

Both sides of every cut-off

Off-by-one defects live at the edge of a range, so a single case in the middle of a range finds nothing. When the body compares a parameter against a literal number, the plan gets a pair: one value crossing the line, one value sitting on the other side of it.

The condition
if (minutesToFirstReply < 0) {throw new RangeError('...');}
if (priority >= 3) {target = target / 2;}
The cases planned
-1 throws RangeError (guard says so)0 returns a boolean (other side)3 halved target (you fill in)2 full target (other side)

Read the difference between those two pairs. The first pair has a known expected value on the failing side, because the guard throws and names the error class. The second pair has none, because the branch changes a local variable and falls through to a shared return. The generated file marks the second pair with a comment saying why, and leaves a loose assertion until you replace it. Reviewers should treat a file still full of loose assertions as unfinished work.

Three depths sit above the editor. Smoke keeps the happy path and the guards with a known answer, which is the set worth running on every commit. Standard adds the other side of each branch. Adversarial adds wrong types, missing arguments and a ten thousand character string for every parameter no guard protects, which is the set to run before a release rather than on every save.

The plan is the deliverable, the file is a draft

Two exports come off the same analysis, and they go to different places.

Sending the plan out before the code gets written is the part QA teams get value from. Agreeing the cases with the developer costs one comment. Discovering after merge that nobody tested the empty list costs a release.

Test names end up in the failure report

A test name is read at its least convenient moment: red text in a pipeline log at the end of the day. The three styles produce the same cases with different names.

StyleProducesFits
shouldshould return null when minutesToFirstReply is not a numberJest and Mocha suites, where the describe block already names the unit
given, when, thengiven minutesToFirstReply is not a number, when firstReplySlaBreach runs, then it returns nullTeams mapping tests back to acceptance criteria
plainreturns null when minutesToFirstReply is not a numberpytest and JUnit, where the method name is already long

Whichever you pick, the condition half of the name comes from the code. A failing test called "returns null when minutesToFirstReply is not a number" points at a line. A failing test called "test2" starts a bisect.

Branch reach is not code coverage

The branch reach figure counts conditions in the pasted function with at least one case aimed at them, divided by the conditions found. That number comes from reading the source text, not from running anything. It says the plan has not skipped a branch. It does not say your assertions are meaningful, that the lines inside each branch ran, or anything at all about the rest of the file.

Real coverage comes from an instrumented run: a Jest or Vitest run with the coverage flag, PHPUnit with Xdebug or PCOV, JUnit with JaCoCo, pytest with coverage. Treat the number here as a checklist tick during planning, then measure properly once the tests run. The test coverage analyzer works on reports from those runs.

Where the generated expectations are wrong

Five failures are worth knowing before a generated file goes near a repository.

A pass through one function

  1. Paste the function with its signature line, and pick the framework matching the language. A fragment of a body with no signature parses as nothing.
  2. Read the plan before the file. Check the parameter types the analyzer inferred, shown under the editor, since a mistyped parameter sends every case in the wrong direction.
  3. Copy the plan into the ticket or the pull request and agree the cases with whoever wrote the function. Rows marked as yours to decide are the conversation.
  4. Switch to Adversarial and look only at the new rows. Each one is a parameter with no guard behind it. Some deserve a test, some deserve a guard in the function instead.
  5. Download the file, replace every loose assertion with the value you agreed, and swap the generic inputs for values from your domain.
  6. Run it with coverage on, then compare what the runner reports against the branch reach shown here. A gap between the two is a branch the plan reached and the assertions did not.

What this generator leaves out

Nothing you paste leaves your browser. Parsing, case planning and code generation all run inside this page, so a function holding business rules or credentials stays on your machine. Load the page once, drop your network connection, and the bench above keeps working.

Questions QA engineers ask about generated unit tests

Boundary pairs, loose assertions, mocking, and how far a generated file should be trusted.

Why do some cases have no expected value?

Because nothing in the function says what the value should be. A guard clause returning null or raising an error states its own outcome, so those cases arrive complete. A branch changing a local variable and falling through to a shared return does not, and no parser reading source text could work out the answer without running the code with your data. Those rows are marked in the plan and carry a comment in the generated file. Leaving them as they are gives you tests passing whatever the function returns, which is worse than no test, because it reads as covered.

Can I trust the generated file without reading it?

No, and no generated test file deserves that. Treat the output as a first draft written by something reading your code closely but understanding none of your domain. The cases are pointed at real branches, the structure matches the framework, and the naming is consistent. The expected values are either read from your own guards or left open. Reviewing the plan takes a minute and is the whole point of the plan view existing separately from the file view.

Which languages and frameworks does this cover?

JavaScript and TypeScript functions produce Jest, Vitest or Mocha with Chai. PHP produces PHPUnit. Java produces JUnit 5. Python produces pytest. The framework dropdown also picks how the pasted code gets parsed, so a mismatch between the dropdown and the language you pasted gives you a failed parse or a bad signature read. Switch the framework first, then paste.

How are boundary values chosen?

From the literal numbers in your comparisons. A check reading less than zero produces minus one on the failing side and zero on the passing side. A check reading greater than or equal to three produces three and two. Each pair sits on opposite sides of the same line, which is where off-by-one defects live. A comparison against a variable rather than a literal cannot produce a pair, since the value depends on state the parser cannot resolve, and those branches still get a case with the expected value left open.

What does the Adversarial setting add?

Cases for parameters no guard protects. For every parameter, if nothing in the body rejects a wrong type, a case passes the wrong type. If nothing checks for null, a case passes null. String and list parameters get a ten thousand character input. These are the cases most likely to fail, and often the right fix is a guard in the function rather than a test recording the current behaviour. Run this set before a release rather than on every commit, since it is deliberately noisy.

Does it mock dependencies for me?

It writes the setup block when the body calls out to something non-deterministic: a network request, the system clock, a random value, a database, or the filesystem. The stubs are named after common patterns, such as a repository or a global fetch, so point them at your real collaborators before running the file. The more useful part is the flag itself. A function reaching the clock or a random source cannot be tested reliably at all until that dependency is passed in rather than reached for.

What is the difference between branch reach and code coverage?

Branch reach counts the conditions in the pasted function with at least one planned case aimed at them. It comes from reading source text, so it says the plan has not skipped a branch and nothing more. Code coverage comes from an instrumented run and reports which lines and branches actually executed. Use branch reach while planning, then measure real coverage once the tests run, and expect the two numbers to differ.

Should the data table option be on?

Turn it on when several cases call the same function with different inputs and known expected values. Those fold into one parameterized block, which is test.each in Jest and Vitest, a data provider in PHPUnit, and parametrize in pytest. It keeps the file short and makes a new case a one line addition. Leave it off when the cases need different setup or different assertions, since forcing those into one table hides what each row is testing.

Is my source code uploaded anywhere?

No. Parsing, case planning and code generation run in JavaScript inside this page. No request goes to any server after the page loads, so a function holding business logic or credentials never leaves your machine. Nothing is stored between visits, and closing the tab clears the editor.