How to Generate Unit Tests with AI for Python and JavaScript

How to Generate Unit Tests with AI for Python and JavaScript
About the Author

Sam Na writes practical guides on AI-assisted coding, reliable digital workflows, and maintainable personal automations. RoutineOS explores how to use modern tools without losing sight of accuracy, clarity, and the behavior that makes a system useful.

Author: Sam Na Contact: seungeunisfree@gmail.com Published: November 2, 2026 Updated: November 2, 2026
AI Unit Testing · Python pytest · JavaScript Jest

You can generate unit tests with AI for Python and JavaScript in minutes, but useful tests require more than runnable code. They need independently defined expected results, meaningful edge cases, and assertions that can reveal when a function behaves incorrectly.

Imagine asking an AI assistant to build a small function for one of your personal workflows. The code looks straightforward, and the sample output matches what you expected. You save it and move on to the next part of your project.

A few days later, a different input produces an unexpected result. The function was not necessarily written with the wrong intention. It simply encountered a condition that nobody had checked before you started relying on it.

Unit tests help address that problem. A unit test exercises a small piece of code under specified conditions and checks whether its behavior matches an expected result. In Python, pytest can run those checks. In JavaScript, Jest provides a similar framework for assertions, grouping tests, and reporting failures.

AI assistants can draft much of the repetitive work. They can suggest normal inputs, identify boundaries, create test functions, and explain unfamiliar assertion syntax. However, they cannot determine the right business rule merely by looking at an implementation that may already contain a mistake.

A test is only useful when its expected result comes from the behavior you require, not from blindly copying whatever the existing code returns.

We will build two separate examples. The Python example tests a capped retry-delay calculation used by automation systems. The JavaScript example tests a function that creates consistent filenames for exported reports. Both examples have ordinary inputs, unusual boundaries, and deliberately invalid values.

The goal is to show how AI-generated test cases become a useful verification tool. You will see how to describe a function's contract, ask AI for targeted tests, execute them with pytest or Jest, and identify assertions that need correction before they can be trusted.

Understand what AI-generated unit tests should prove

Generating tests is easy when the only requirement is to produce a file that a test runner can execute. The harder part is ensuring that each test represents behavior the application genuinely needs.

A test can pass while checking an unimportant detail. It can also fail because its expected value was invented incorrectly. Neither outcome provides reliable evidence about the function's real purpose.

A unit test checks a behavior, not an AI explanation

Consider a function that calculates how long an automation should wait before retrying a failed operation. You might expect the waiting time to increase with each attempt, but only up to a specified maximum.

A useful test supplies an attempt number and checks the exact delay required by that rule. It does not merely check whether the function returns a number. An incorrect delay can still be a perfectly valid number.

Similarly, a JavaScript function that builds a filename should not pass its test simply because it returns a string. The string needs to follow the agreed naming format, preserve acceptable characters, and handle invalid input consistently.

This distinction is called the test oracle problem. A test needs a trustworthy basis for deciding what the correct output should be. AI can write the assertion, but you must supply or approve the rule behind that assertion.

Weak assertion
The function returns something

A test only checks whether a result exists or whether the returned value has a particular data type.

It might pass even when the calculation or output format is wrong.

Meaningful assertion
The result satisfies a defined requirement

The test checks the precise output for a known input and verifies an important boundary or error condition.

A genuine behavioral mistake has a reason to make the assertion fail.

Keep unit tests focused on one small responsibility

A unit test usually examines a function, method, or similarly small component in a controlled environment. The goal is to understand its behavior without depending unnecessarily on unrelated application components.

If a function calculates a delay, it should not need a real network failure to prove that the calculation works. If another function formats a filename, it should not need to write an actual file simply to verify the returned name.

That separation makes tests faster to understand and easier to repeat. When a test fails, you can focus on the behavior being checked instead of first investigating external services or environmental conditions.

Some functions do depend on clocks, databases, or external APIs. In those cases, a mock or controlled substitute may help isolate the unit. The substitute must still represent the relevant interaction accurately; otherwise, the test may pass only because it describes an unrealistic environment.

Distinguish unit tests from broader automated checks

A unit test of a report filename function can check its return value without creating a file. An integration test might check whether the file-writing component uses that name correctly. An end-to-end test could exercise the complete export process.

These tests answer different questions. They complement one another, but adding several complete workflows to a unit test can make a simple failure difficult to diagnose.

For this guide, we will keep the main examples deterministic. The same inputs should produce the same outputs, without a network request or reliance on the current clock. That makes them suitable starting points for someone learning AI Python unit testing or JavaScript testing.

Choosing the right test level
Match the check to the behavior

Unit: Does a retry calculation return the required delay?

Integration: Does the surrounding job use the calculated delay when a retry is scheduled?

End-to-end: Does the complete workflow behave correctly under controlled failure conditions?

Key Takeaway

A unit test should check a specific requirement using an independently justified expected result. Keep each test focused on the behavior of the component rather than treating successful execution as proof of correctness.

Design useful test cases before generating code

AI is particularly helpful when you already know what a function should do but have not yet considered all the inputs it might receive. The assistant can suggest additional cases and organize them into a clear test plan.

The quality of that plan depends on the rules you provide. If the contract is unclear, the AI may invent expectations for empty values, invalid types, or boundary conditions.

Start with normal input and an exact expected output

A normal case demonstrates the main purpose of the function. It should use realistic input and produce a result you can calculate or explain independently.

For example, a retry-delay function might begin with a two-second delay and double the waiting time on later attempts. The first few expected results can be established directly from that rule before writing any implementation.

Ask AI to suggest several representative cases, but calculate the expected values yourself or verify them from the written contract. The assistant should not use the function's current output as the sole source of truth.

Once the ordinary behavior is clear, examine conditions close to a meaningful limit. Those are often more revealing than adding many similar everyday examples.

Add boundaries, empty data, and invalid values

A boundary is a point where the required behavior changes. A retry delay may increase until it reaches a cap, then stop growing. The attempt immediately before that cap and the first attempt that reaches it are especially useful cases.

Empty data needs a rule of its own. For a list formatter, an empty list might legitimately return an empty result. For a required filename label, an empty string should probably be rejected. Neither decision follows automatically from the word "empty."

Invalid values can include the wrong data type, missing arguments, negative numbers, malformed dates, or unexpected characters. The test should establish whether the function raises an exception, returns a documented fallback, or handles the value in another explicitly defined way.

Normal behavior
Expected inputs

Check the result for representative values the function is intended to accept.

Boundary behavior
Values around a limit

Check the last value before a rule changes, the value at the boundary, and the next relevant value.

Empty behavior
Missing or blank information

Determine whether an empty string, collection, or missing argument has a valid meaning.

Invalid behavior
Values outside the contract

Test inappropriate types, prohibited ranges, and malformed input using the specified error policy.

Write the expected behavior before creating assertions

A useful test plan can begin as ordinary English. Describe each input, its expected result, and the reason that result follows from the function's rules. Only then ask AI to translate the cases into pytest or Jest.

This order helps expose contradictions in the requirements. Suppose one proposed case says an empty filename label should produce a default name, while another says empty labels must raise an exception. The conflict needs a decision before both assertions enter the test suite.

You can also classify cases by the particular condition they examine. That makes it easier to notice when five generated tests all exercise one branch while another important branch remains untouched.

1
State the contract
Describe valid inputs, expected outputs, and behavior that must be rejected.
2
Identify branches
Find conditions that lead to different results or exceptions.
3
Choose representative cases
Include ordinary values, meaningful boundaries, and relevant invalid inputs.
4
Approve expected results
Confirm the required result without relying solely on the implementation.
5
Generate the test code
Ask AI to express the approved cases using the project's framework.

Do not let an AI assistant invent a fallback value just because an input seems inconvenient. Empty and invalid data must follow the application's intended behavior, not a default policy the model assumed.

Key Takeaway

Build a small set of behavior requirements before generating test code. Normal inputs, boundaries, empty values, and invalid data should each have a justified expected result.

Ask AI coding assistants to generate focused tests

ChatGPT, Claude, Gemini, Grok, and GitHub Copilot can help draft unit tests when supplied with relevant code and clear expectations. The interface and available project context vary, but the underlying instructions should remain precise.

A useful AI unit test generator should produce tests that are readable, independently executable, and consistent with the framework already used by your project.

Give AI the function contract and testing framework

Start by naming the target language and test runner. A Python project using pytest needs different syntax and conventions from a JavaScript project using Jest.

Provide the function signature, relevant source code, expected outputs, and rules for invalid input. If your project already has tests, include a short example of their naming and import conventions.

Ask the assistant to create independent tests. A test should not need another test to run first or rely on state left behind by a previous case. Such dependencies make failures difficult to interpret.

Copy-Ready Prompt: Generate Unit Tests with AI
You are helping me create focused unit tests. Language: [PYTHON OR JAVASCRIPT] Framework: [PYTEST OR JEST] Function under test: [PASTE THE RELEVANT CODE] Required behavior: [DESCRIBE THE FUNCTION CONTRACT] Approved expected results: [LIST INPUTS AND EXPECTED OUTPUTS] Invalid-input policy: [LIST INPUTS THAT MUST BE REJECTED] Please generate tests that: 1. Verify the main successful behavior. 2. Exercise meaningful boundary values. 3. Check empty input where relevant. 4. Verify required exceptions. 5. Use descriptive test names. 6. Remain independent of execution order. 7. Avoid real network calls and production data. 8. Use the stated framework and project imports. Before writing code, identify any missing requirements. Do not invent execution results. Do not infer the expected answer solely from the implementation. Do not add unrelated production code.

You can use the same general request across several AI assistants. However, it is more useful to review one focused test draft carefully than to collect dozens of nearly identical generated tests.

Choose the assistant according to your working environment

A chat-based assistant is convenient when the function is small enough to share with its contract. You can ask it to create a test plan, explain an assertion, or suggest cases you may have missed.

An IDE-integrated assistant can be convenient when the source file and existing tests are already part of a project. It may use available editor context to align imports, file names, and test conventions. The exact capabilities depend on the tool and its configuration.

ChatGPT
Draft cases from a written contract

Useful for turning behavior requirements into pytest or Jest tests and discussing why particular assertions matter.

Claude
Examine complex conditions

Useful for organizing related behaviors, questioning ambiguous cases, and drafting readable test code.

Gemini
Work from supplied code context

Can help explain test requirements and produce assertions. Gemini Code Assist also supports coding workflows in compatible development environments.

Grok
Request alternative edge cases

Can provide another perspective on possible inputs and expected behavior when supplied with an explicit function contract.

GitHub Copilot is also relevant to this workflow. Its official documentation describes generating unit tests with Copilot Chat, including the need for specific requirements and test verification.

The GitHub Copilot testing guide explains ways to create test cases and improve generated test coverage. Available commands and features can differ across supported editors and product versions.

Ask AI what the first draft is missing

Once an assistant has generated a test file, do not immediately ask it for more tests. First inspect which requirements the existing assertions actually cover.

The draft might handle the ordinary return value but ignore an exception that the function must raise. It might cover an upper limit without checking the value immediately below it.

Ask the assistant to identify missing cases against the original contract. A request focused on gaps is usually more productive than asking for a large number of additional tests.

Copy-Ready Prompt: Find Missing Test Cases
Review the completeness of this unit test suite. Function contract: [PASTE REQUIREMENTS] Existing test code: [PASTE TEST FILE] Identify: - Requirements with no corresponding assertion - Important boundary values not covered - Invalid inputs that should be rejected - Redundant cases that add no useful distinction - Tests that repeat the implementation's assumptions - Dependencies that make tests order-sensitive Recommend only the most valuable missing cases. Do not rewrite the production function. Do not claim the tests were executed. Explain the requirement supported by each proposed test.

The assistant's review is a starting point. You still need to confirm that every new expected result matches the behavior your application is supposed to provide.

Key Takeaway

Give AI a precise function contract, framework, and set of approved examples. Use its first draft to establish coverage, then request missing cases rather than unnecessary repetition.

Generate and run Python unit tests with pytest

Our Python example calculates an increasing delay between attempts to retry a failed operation. The function does not perform a retry or contact an external service. It only calculates how many seconds a caller should wait.

This separation makes the function a good candidate for unit testing. Its outputs can be checked directly, and no real failure needs to occur during the test.

Define a retry-delay contract

We will use a one-based attempt number. The first attempt receives the base delay. Later attempts double the previous delay, but the result cannot exceed the configured cap.

The default base is two seconds, and the cap is thirty seconds. The expected sequence therefore begins at two, four, eight, and sixteen seconds, before reaching the thirty-second limit.

All three parameters must be integers. The attempt number must be positive, the base must be at least one second, and the cap must not be smaller than the base.

We will reject boolean values even though Python treats bool as a subclass of int. A boolean has no intended meaning as a retry attempt or delay setting in this example.

Python behavior contract
retry_delay(attempt, base_seconds, cap_seconds)

Inputs: Three integers satisfying the stated range rules.

Output: A positive integer delay in seconds, no larger than the cap.

Invalid types: Raise TypeError.

Invalid ranges: Raise ValueError.

Write the Python function that the tests will check

Save the implementation as backoff.py. This version validates inputs before calculating the delay. Its loop stops as soon as the cap is reached, so an unusually large attempt number does not require a similarly large number of iterations.

def retry_delay(attempt, base_seconds=2, cap_seconds=30):
    for name, value in (
        ("attempt", attempt),
        ("base_seconds", base_seconds),
        ("cap_seconds", cap_seconds),
    ):
        if type(value) is not int:
            raise TypeError(f"{name} must be an integer")

    if attempt < 1:
        raise ValueError("attempt must start at 1")

    if base_seconds < 1:
        raise ValueError("base_seconds must be positive")

    if cap_seconds < base_seconds:
        raise ValueError(
            "cap_seconds must be at least base_seconds"
        )

    delay = base_seconds

    for _ in range(attempt - 1):
        delay = min(delay * 2, cap_seconds)

        if delay == cap_seconds:
            break

    return delay

The loop expresses the contract without requiring an external scheduler. It begins with the base delay, doubles it for each subsequent attempt, and stops growing once the configured maximum is reached.

This calculation is not a complete retry policy. A real system may also need a maximum number of attempts, random jitter, and rules for identifying which errors are safe to retry. Those concerns belong to the surrounding application.

Generate pytest cases for normal and invalid behavior

pytest provides @pytest.mark.parametrize to run the same test function against multiple sets of inputs. This is useful when different values exercise the same behavior rule.

Instead of writing a separate function for every attempt number, we can place the approved inputs and expected results together. pytest then runs each parameter set as an individual test case.

We also need to verify exceptions. pytest.raises() checks whether a specified exception occurs. A case expecting ValueError should not pass merely because the function raises a different error.

These features are documented in the official pytest parametrization guide. Save the following test suite as test_backoff.py beside the function.

import pytest

from backoff import retry_delay


@pytest.mark.parametrize(
    "attempt,expected",
    [
        (1, 2),
        (2, 4),
        (3, 8),
        (4, 16),
        (5, 30),
        (10, 30),
    ],
)
def test_default_schedule(attempt, expected):
    assert retry_delay(attempt) == expected


@pytest.mark.parametrize(
    "attempt,base,cap,expected",
    [
        (1, 3, 9, 3),
        (2, 3, 9, 6),
        (3, 3, 9, 9),
        (9, 3, 9, 9),
    ],
)
def test_custom_limits(attempt, base, cap, expected):
    assert retry_delay(attempt, base, cap) == expected


@pytest.mark.parametrize(
    "attempt,base,cap",
    [
        (0, 2, 30),
        (-1, 2, 30),
        (1, 0, 30),
        (1, 5, 4),
    ],
)
def test_invalid_ranges(attempt, base, cap):
    with pytest.raises(ValueError):
        retry_delay(attempt, base, cap)


@pytest.mark.parametrize(
    "attempt,base,cap",
    [
        (True, 2, 30),
        (1, 2.5, 30),
        (1, 2, None),
    ],
)
def test_invalid_types(attempt, base, cap):
    with pytest.raises(TypeError):
        retry_delay(attempt, base, cap)


def test_large_attempt_caps_without_growing_indefinitely():
    assert retry_delay(1_000_000) == 30

The suite covers more than the ordinary increasing sequence. It checks a custom base and cap, values that violate the input rules, and a very large attempt number.

The large-attempt case has a specific purpose. Because the calculation reaches its cap after only a few doublings, the function should not keep processing every theoretical attempt. The test confirms the returned result, although it does not by itself measure performance.

Notice that the tests do not copy the loop into their assertions. They compare the function's outputs with expected values established from the written rule. This makes the test suite less dependent on the implementation's particular structure.

Run pytest and inspect the actual results

Install pytest in a suitable Python environment if it is not already available. From the directory containing both files, run the test module.

python -m pip install pytest

python -m pytest -q test_backoff.py

The displayed Python implementation and test suite were executed together in a local environment using Python 3.13 and pytest 9.0.2. All eighteen collected test cases passed.

The successful cases include six default delay checks, four custom-limit checks, four invalid-range checks, three invalid-type checks, and the large-attempt example.

18/18 pytest cases passed The displayed Python tests were executed against the accompanying function.
4 Main test groups Default delays, custom limits, invalid ranges, and invalid types.
1 Additional large-input check Confirms the capped result for an unusually large attempt number.

A passing result means the configured assertions succeeded under the conditions tested. It does not establish that a real network operation will retry correctly or that the surrounding job will respect the calculated delay.

If a test fails, read the assertion and the values involved before changing production code. Check whether the expected result follows from the function's contract. Generated tests can contain wrong expectations too.

Key Takeaway

AI can draft a pytest suite from a precise behavior contract. Parametrized cases make input variations manageable, while exception assertions protect defined error behavior. The actual pytest run, not the generated test text, establishes which cases passed.

Create JavaScript unit tests with Jest

The JavaScript example has a different purpose. It creates a predictable filename for a report exported from a personal productivity workflow.

The function receives a report label and a JavaScript Date. It normalizes the label into a readable filename component and appends a date in a consistent format.

Filename generation may seem simple, but several details need explicit rules. What happens to punctuation? Should non-English letters be preserved? Which calendar date should appear when a timestamp crosses time zones?

Define the export filename behavior

For this example, labels are strings containing at least one letter or digit after normalization. We will convert them to lowercase, keep Unicode letters and numbers, and replace runs of other characters with a single hyphen.

Leading and trailing hyphens will be removed. An empty or symbol-only label is invalid because it cannot produce a meaningful filename component.

Dates must be valid JavaScript Date objects. The filename will use the UTC calendar date rather than the machine's local calendar date. This is a deliberate choice for consistent exports across different time zones.

The function only returns a filename. It does not create files, check for existing names, or guarantee that the result will be suitable for every possible filesystem. Those responsibilities remain outside this unit.

JavaScript behavior contract
createExportName(label, date)

Valid label: A string that contains at least one Unicode letter or number.

Valid date: A Date object representing a valid point in time.

Output: A normalized label, UTC date, and .csv extension.

Invalid inputs: Raise TypeError or RangeError according to the failure.

Implement a deterministic JavaScript function

Save the following code as export-name.cjs. The CommonJS module format keeps the example simple for a Node.js project configured to use CommonJS.

The function validates the date before calling toISOString(). That matters because an invalid Date object can exist without representing a valid point in time.

function createExportName(label, date) {
  if (typeof label !== "string") {
    throw new TypeError("label must be a string");
  }

  if (!(date instanceof Date)) {
    throw new TypeError("date must be a Date");
  }

  if (Number.isNaN(date.getTime())) {
    throw new RangeError("date must be valid");
  }

  const slug = label
    .trim()
    .normalize("NFKC")
    .toLowerCase()
    .replace(/[^\p{L}\p{N}]+/gu, "-")
    .replace(/^-|-$/g, "");

  if (!slug) {
    throw new RangeError(
      "label must contain letters or digits"
    );
  }

  return `${slug}-${date.toISOString().slice(0, 10)}.csv`;
}

module.exports = { createExportName };

Unicode property escapes allow the expression to recognize letters and numbers beyond basic English characters. The example uses JavaScript features available in modern Node.js environments.

The use of UTC also gives the tests a stable interpretation of the supplied date. A timestamp that falls late at night in one time zone may belong to the next UTC calendar day. The expected filename should follow the contract rather than the computer's locale.

Generate Jest tests with ordinary and unusual inputs

Jest provides test(), expect(), and matchers for comparing results or checking exceptions. It also supports test.each() for running the same assertion with several parameter sets.

In this example, we will verify normal labels, punctuation, Unicode characters, UTC date behavior, invalid labels, and invalid Date values.

We will also check that calling the function does not change the supplied Date object. That expectation belongs to the function's contract even though the main purpose is filename generation.

The official Jest Getting Started guide explains basic test files, matchers, installation, and execution. Save the following suite as export-name.test.js.

const { createExportName } = require("./export-name.cjs");

const fixedDate = new Date("2026-11-02T12:00:00Z");


test.each([
  ["Monthly Review", "monthly-review-2026-11-02.csv"],
  ["  Focus & Flow  ", "focus-flow-2026-11-02.csv"],
  ["Café Notes", "café-notes-2026-11-02.csv"],
  ["📁 Build Logs", "build-logs-2026-11-02.csv"],
])("formats label %p", (label, expected) => {
  expect(createExportName(label, fixedDate)).toBe(expected);
});


test("uses the UTC date across local offsets", () => {
  const date = new Date("2026-11-02T23:30:00-05:00");

  expect(createExportName("Night Run", date))
    .toBe("night-run-2026-11-03.csv");
});


test.each(["", " - ! ", "🗂"])(
  "rejects an empty normalized label: %p",
  (label) => {
    expect(() => createExportName(label, fixedDate))
      .toThrow(RangeError);
  }
);


test.each([null, 42])(
  "rejects a non-string label: %p",
  (label) => {
    expect(() => createExportName(label, fixedDate))
      .toThrow(TypeError);
  }
);


test("rejects a missing date", () => {
  expect(() => createExportName("Report", null))
    .toThrow(TypeError);
});


test("rejects an invalid Date", () => {
  expect(() => createExportName("Report", new Date("invalid")))
    .toThrow(RangeError);
});


test("does not change the supplied date", () => {
  const date = new Date("2024-02-29T12:00:00Z");
  const before = date.getTime();

  expect(createExportName("Leap Day", date))
    .toBe("leap-day-2024-02-29.csv");

  expect(date.getTime()).toBe(before);
});

The test suite contains twelve scenarios. Four check ordinary formatting behavior, one checks a UTC date boundary, and the remaining cases examine invalid values and preservation of the Date object.

The UTC boundary case is especially important. The supplied timestamp represents late evening on November 2 in a negative-five-hour offset, but its UTC date is November 3. The expected filename follows the UTC rule.

Another useful case preserves the accented letter in Café Notes. An AI assistant that assumes every filename must be ASCII-only might generate a different expectation. The specified Unicode contract prevents that unnecessary change.

Run Jest and distinguish verification from prediction

If your project does not already include Jest, install it as a development dependency. The example assumes Node.js and a compatible Jest configuration.

npm install --save-dev jest

npx jest export-name.test.js --runInBand

The command runs the named test file. The --runInBand option requests serial execution, which is convenient for a small example even though these tests do not require execution in a particular order.

The JavaScript function was checked separately under Node.js 22.16.0. Twelve corresponding behavior scenarios passed using Node.js assertions.

The complete Jest suite was not executed in an installed Jest environment during this check. The Node.js result verifies the underlying function behavior for the selected inputs, not Jest's own test collection or reporting.

Run the supplied Jest command in your configured project and inspect its actual output. If an import, configuration, or test-runner error appears, resolve that issue before treating the suite as successfully executed.

12 Jest test scenarios Formatting, UTC behavior, invalid inputs, and Date preservation.
12/12 Node.js behavior checks The corresponding scenarios passed under Node.js assertion-based verification.
— Jest runner result Not independently executed in the verification environment.

A test that passes in your environment supports the behavior actually asserted. It does not check whether a filename is unique, whether an export completes, or whether a remote storage provider accepts the result. Those are different requirements requiring their own checks.

Key Takeaway

Jest can express normal, boundary, and invalid-input cases clearly. Define formatting and date rules before generating assertions, and distinguish actual test-runner results from checks performed through another verification method.

Evaluate AI-generated tests and their results

A generated test suite needs review even when its syntax is valid. The important question is whether the assertions meaningfully examine the function's contract.

Test generation should reduce repetitive writing, not transfer the authority to define correctness from the developer to the assistant.

Reject assertions that merely agree with the implementation

Imagine a retry function that accidentally returns four seconds on its first attempt when the written contract requires two. If an AI reads the faulty implementation and generates a test expecting four, the test may pass while preserving the mistake.

This problem is especially common when the prompt asks the assistant to "write tests for this code" without describing what the code is supposed to accomplish.

Review each expected result against a requirement, an independently calculated example, or another reliable specification. If you cannot explain why the expectation is correct, do not assume that a passing assertion is useful.

Implementation-derived expectation
"The code returns 4, so expect 4"

This can turn an existing defect into a passing test. The expected value lacks an independent justification.

Contract-derived expectation
"Attempt 1 uses the defined base of 2"

The expected value follows a stated rule, so an implementation returning 4 will trigger a meaningful failure.

Use coverage information without treating it as proof

Code coverage tools can report which statements or branches were exercised during a test run. This information helps identify code paths that have not been checked.

However, coverage does not establish that the assertions are correct. A test could execute every line of a function while checking almost nothing about its output.

Consider a test that calls a filename formatter and then asserts that the result is a string. It may execute the normalization logic without verifying whether the actual filename follows the required format.

When using coverage, examine untested behavior and the quality of the expectations together. A report showing a high percentage is not a substitute for understanding what the tests actually prove.

Ask AI to explain which contract rules lack assertions, not merely which source lines have not been executed. This keeps the review focused on user-visible behavior.

Check test independence, side effects, and execution evidence

A reliable unit test should not depend on the result of another test unless that relationship is part of a deliberately designed testing scenario.

Tests that share mutable data can become order-sensitive. One case changes an object, and a later case receives the modified value instead of its intended starting state.

AI-generated tests can also introduce unnecessary file writes, network calls, or dependence on the current date. Those actions make results harder to reproduce and can affect data outside the test.

Prefer explicit input values, isolated test data, and controlled dependencies. If a test needs the current time, consider providing a fixed clock or fixed date through a suitable interface.

Each assertion has a documented or independently justified expected result.
The main success path is represented by a realistic input.
Important boundaries have been selected intentionally.
Empty and invalid values follow the intended error policy.
Tests do not rely on the execution order of unrelated cases.
Real accounts, production files, and remote services are not used unnecessarily.
Test-runner output is recorded accurately, including failures and skipped cases.
The suite remains understandable without referring back to the original AI conversation.

If AI generates twenty tests that all check closely related ordinary inputs, you may gain less useful protection than from a smaller suite that covers distinct requirements.

Test count is therefore a description of how many cases were executed, not a quality score. The right number depends on the function's possible behaviors and the importance of its failure modes.

Use AI to expand the range of questions your tests ask. Use the function contract and real execution results to decide whether those questions have been answered correctly.

Key Takeaway

Review AI-generated assertions as carefully as generated code. Independent expectations, meaningful conditions, isolated execution, and accurately reported results matter more than the size of the test suite.

Frequently Asked Questions

Q1. Can ChatGPT generate unit tests for Python and JavaScript?
Yes. ChatGPT can draft Python unit tests using pytest or JavaScript unit tests using Jest when you provide the relevant code and expected behavior. Review the assertions and execute the tests in your own environment. Generated tests are proposals, not evidence that the code has passed.
Q2. Which AI tool is best for generating unit tests?
The appropriate choice depends on the project and the context the tool can access. ChatGPT, Claude, Gemini, Grok, and GitHub Copilot can all assist with test design or code generation. An IDE-based tool may be convenient for existing projects, while a conversational assistant can work well with a small function and written contract. Evaluate the resulting tests rather than assuming one tool is universally more reliable.
Q3. How do I generate unit tests with GitHub Copilot?
Open the target code in a supported development environment and ask Copilot Chat to create tests using your project's framework. Provide expected outputs, invalid-input rules, and important boundaries. GitHub's official documentation also describes test generation workflows and the importance of reviewing suggested tests.
Q4. What is the difference between pytest and Jest?
pytest is a Python testing framework, while Jest is commonly used for JavaScript testing. Both support assertions, organized test cases, and automated execution. Their syntax, configuration, and ecosystem integrations differ, so use the framework appropriate to your project and language.
Q5. How many unit tests should AI generate for one function?
There is no universal number. Start with representative successful inputs, meaningful boundaries, and required error conditions. Add cases when they protect distinct behavior rather than repeating the same expectation with different values. A compact, well-designed suite can be more useful than a large collection of redundant assertions.
Q6. Can AI-generated unit tests be wrong even if they pass?
Yes. A test can pass while checking an incorrect expected result or an unimportant property. This can happen when AI infers the expected answer from faulty source code. Compare each assertion with an independently defined requirement before treating the result as meaningful.
Q7. Do I need to test empty input and invalid values?
Test them when they are relevant to the function's contract. An empty collection may be valid for one function, while an empty required label may need to raise an exception. Invalid types and out-of-range values should follow explicit rules rather than behavior invented by the test generator.
Q8. Does passing every unit test mean my code is bug-free?
No. Passing tests show that the configured assertions succeeded under the tested conditions. They cannot prove that every possible input, environment, or integration will behave correctly. Use the results alongside appropriate code review, broader testing, and operational checks where needed.

Conclusion: turn AI-generated tests into useful evidence

AI can make writing unit tests less repetitive, especially when a function has several input variations and error conditions. It can draft test files, explain framework syntax, and suggest boundaries that deserve attention.

The important work begins with defining the behavior you expect. A good assertion is not simply a prediction of what existing code will do. It is a check against a requirement you can justify.

The Python retry-delay example demonstrated how pytest can check ordinary calculations, capped behavior, input validation, and large inputs. Its eighteen cases passed in the local pytest run.

The JavaScript export example showed why formatting rules, Unicode support, and time zone decisions must be explicit before writing Jest assertions. Twelve corresponding behaviors were verified under Node.js, while the Jest runner itself still requires execution in a configured project.

You do not need to generate a large test suite for every small function. Start with one behavior that matters, identify its main failure conditions, and ask AI to express those conditions as readable assertions.

When you can explain why each expected result is correct and show the actual test output, the suite becomes useful evidence rather than another piece of generated code you have to trust.

Your Next Step: Generate Tests for One Useful Function

Choose a small Python or JavaScript function you already use. Write down its required behavior and three distinct input conditions: one ordinary case, one meaningful boundary, and one invalid input. Ask an AI assistant to generate pytest or Jest tests from those requirements. Review the expected results, run the tests locally, and correct any assertion that does not match the contract. Keep the resulting test file with the function so its behavior remains easy to check.

About Sam Na

Sam Na writes RoutineOS guides on AI-assisted productivity, practical programming workflows, and reliable personal automation. His articles focus on clear requirements, understandable examples, and verification methods that help readers make better use of their everyday digital tools.

Sam Na · RoutineOS seungeunisfree@gmail.com
A Quick Note Before Using These Examples

This article provides general information and educational programming examples. The appropriate testing approach depends on your code, environment, data, and the behavior your application is required to provide.

Run new tests with safe sample data and review any command that could affect important files, accounts, or external services. For systems with significant operational or security consequences, check current official documentation and consult a qualified developer or security professional when appropriate.

References and Official Documentation
pytest Documentation — How to Parametrize Fixtures and Test Functions

Official guidance on running test functions with multiple parameter sets and organizing different input scenarios.

pytest parametrization documentation
Jest Documentation — Getting Started

Official documentation covering installation, basic test files, matchers, and the steps for running JavaScript tests.

Jest Getting Started guide
GitHub Docs — Writing Tests with GitHub Copilot

Official examples of using GitHub Copilot to develop unit and integration tests, provide test requirements, and improve test coverage.

GitHub Copilot testing guide
Previous Post Next Post