How to Prompt AI to Generate Edge Cases for Testing

A copyable prompt template that forces AI to enumerate real edge cases, nulls, boundaries, concurrency, unicode, and timezones, plus a worked example against a discount code validator.

Steve Jefferson
Steve Jefferson
Developer Advocate
28 August 20261 min read

If you ask an AI model to write test cases, it will hand you the happy path and a couple of obvious failures, then stop. That is because "write test cases for this function" is an underspecified request, and the model fills the gap with the cases every function has, not the cases that actually break yours. To get real edge cases you have to prompt for them category by category: null and empty inputs, boundary values, malformed and oversized data, concurrency, unicode, and timezone edges. This guide gives you a copyable prompt template that walks through each category, plus a worked example so you can see the difference between the default output and the directed output side by side.

Why the default output is thin

Language models are trained on a huge volume of code and test files, and the statistically common test case for any given function is the one that exercises the intended behavior. When you ask for "tests" or "edge cases" without more context, the model reaches for that median example: valid input in, expected output out, maybe one obvious rejection. It is not being lazy. It genuinely does not know which boundaries matter to your system unless you tell it. A discount code that is case sensitive in your database but not in your validation function is an edge case only you know about until you say so.

This is a different problem from generating realistic sample data to fill a staging database. That is covered in how to prompt AI to generate realistic test data, which is about producing plausible, varied records for demos and load testing. What you want here is the opposite instinct: not plausible data, but adversarial data. Inputs specifically chosen to find the seams where your code assumes something that is not actually guaranteed.

The named prompt template: the Boundary Sweep

Copy this structure into your prompt whenever you hand a function to an AI model for test generation. It forces the model through each category instead of letting it stop after the obvious cases.

  1. Paste the exact function signature and a short description of its contract: what each parameter is supposed to be, what it returns, and what it is allowed to throw or reject.

  2. Tell the model explicitly: "Do not give me the happy path. Give me only edge cases, grouped under these headings: null/empty/missing, boundary values, malformed or oversized input, concurrency or race conditions, unicode and encoding, and timezone or locale."

  3. For each heading, ask for at least two concrete cases with the exact input value and the expected behavior, not a vague description like "handles bad input gracefully."

  4. Ask the model to flag any case where it is guessing at your intended behavior rather than testing a documented contract, so you can confirm or correct it before writing the assertion.

  5. Ask for the cases as runnable test stubs in your framework of choice, not prose, so you can drop them straight into a test file and fill in the assertions.

Worked example: a discount code validator

Here is a plausible function you might hand to a model. It checks whether a discount code is valid for a given cart.

function validateDiscountCode(code, cart, now = Date.now()) {
  // code: string, e.g. "SAVE20"
  // cart: { items: Array<{sku, qty, price}>, subtotal: number, currency: string }
  // now: epoch ms, used to check code.expiresAt
  // returns { valid: boolean, discountAmount?: number, reason?: string }
}

Ask a model "write test cases for validateDiscountCode" with no further direction and you will typically get something like this, regardless of which model you use.

What the default prompt produces

  1. A valid, unexpired code applied to a cart above the minimum spend returns valid: true with the correct discount amount.

  2. An unknown code returns valid: false with a "code not found" reason.

  3. An expired code returns valid: false.

  4. An empty cart returns valid: false.

Four cases, all of them the ones you would have written yourself in thirty seconds. Nothing here would have caught a real production bug. Now run the Boundary Sweep template against the same signature.

What the directed prompt produces

Null, empty, and missing: code is null, code is an empty string, code is only whitespace, cart.items is an empty array but subtotal is nonzero (a data inconsistency the function should probably reject rather than trust), cart itself is undefined.

Boundary values: cart.subtotal is exactly equal to the code's minimum spend threshold (off-by-one errors live here: is the boundary inclusive or exclusive?), a discount percentage of exactly 100 that would make the order free, a discount that would make discountAmount negative due to a rounding error, now equal to expiresAt to the millisecond.

Malformed or oversized input: code containing SQL-injection-style characters if codes are ever used in a raw query, a code string of 10,000 characters, cart.items with 50,000 line items to check for performance collapse, price values that are negative or NaN, currency mismatched between the code's configured currency and the cart's currency.

Concurrency and race conditions: two requests redeeming the same single-use code within milliseconds of each other, a code that gets deactivated by an admin in another process between the read and the redemption, stock or usage-count decrements that are not atomic, causing a code to be usable one more time than its limit allows under concurrent load.

Unicode and encoding: a code entered with full-width unicode digits instead of ASCII digits, a code with mixed case where the store's business rule is that codes are case-insensitive but the database comparison is case-sensitive, a code containing a right-to-left override character pasted in from another system, cart item names with emoji or combining diacritical marks that could break a receipt template downstream.

Timezone and locale: expiresAt stored as a date-only value like 2026-08-28 with no timezone, evaluated against now in UTC versus the customer's local timezone, meaning a code marked to expire "today" behaves differently depending on which side of midnight UTC the server and customer are on, a daylight saving transition day where "24 hours before expiry" is actually 23 or 25 hours.

That is the actual gap. The default prompt tests that the function works. The directed prompt tests where the function's assumptions stop being true, which is where real bugs live.

A second worked pattern: date-range overlap

The same template applies to any function with implicit boundaries, not just validators. Take a booking system's overlap checker.

function rangesOverlap(rangeA, rangeB) {
  // rangeA, rangeB: { start: Date, end: Date }
  // returns boolean
}

Without direction, a model tests two ranges that clearly overlap, two that clearly do not, and maybe one that is fully nested inside the other. Directed at boundary values specifically, it will also produce: rangeA.end exactly equals rangeB.start (is the boundary a shared endpoint an overlap or not, and does your business logic agree with what the code does), start after end within the same range object, a zero-length range where start equals end, and a range spanning a leap second or a DST fall-back hour where the same wall-clock time occurs twice. Directed at concurrency, it will produce two bookings created in the same transactionless window that each pass the overlap check independently because neither sees the other's write yet, double-booking the resource. None of that shows up until you ask for it by name.

Making it stick in your workflow

Save the Boundary Sweep template as a snippet in your editor or prompt library so you are not retyping the six categories every time. When you review the output, treat the model's flagged guesses as a checklist item, not a formality. If it flags that it does not know whether your discount codes are case-sensitive, that is a real open question in your spec, not the model being unhelpful, and it is worth resolving before you write the assertion either way.

It also helps to pair this with how to prompt AI to write a test plan, since a test plan gives you the higher-level map of what needs coverage, while the Boundary Sweep is what you run against each individual function once you know it needs testing. For functions with unclear or fuzzy string inputs, how to prompt AI to write a regular expression is worth reading alongside this one, since malformed-input edge cases and regex edge cases overlap heavily. And if you want the model to show its work on why it picked a given boundary case rather than just handing you a list, how to prompt AI to explain its reasoning before it answers is a useful technique to bolt onto the template above.

This approach sits inside a broader habit of writing specific, structured prompts rather than open-ended ones. If you want the underlying framework this template is built on, see the prompt engineering pillar guide for the general principles behind why category-by-category direction outperforms a single vague instruction across every kind of prompt, not just testing.

None of this replaces a human thinking about what could go wrong. It just means the AI model does the first pass of category-by-category enumeration for you, faster and more completely than doing it from memory, so long as you tell it which categories to check.

How did this land?

About the author

Steve Jefferson
Steve Jefferson

Developer Advocate

Steve builds something with Swarmz every week and writes up what worked, what broke, and what he'd do differently. Tutorials and hands-on guides are his lane.

Share

Get the next post in your inbox

One email a month. Product updates, engineering posts, and the best of Built with Swarmz.

I agree to receive emails about AI building tips and Swarmz product news. Unsubscribe any time.

How to Prompt AI to Generate Edge Cases for Testing | swarmz.net