Property-Based Testing
By Syncfusion®, Inc. · Last updated
Write rules that must hold for every input, let fast-check try hundreds of random inputs, and watch shrinking find the smallest one that breaks the AI's code. Bounds, sums, round trips and no-change properties.
What a Property Is
So far, every test checked one example: "10% off ₹500 is ₹450".
Examples are good. But you only test the inputs you thought of. AI code often breaks on the inputs nobody thought of.
Property = a rule that must be true for every input, not just one.
For example:
- A discount never makes the price negative.
- A discount never makes the price bigger.
- The shares of a split bill always add up to the total.
Property-based testing = a tool makes hundreds of random inputs and checks the rule on each one.
Simple lesson: an example checks one input. A property checks a rule on hundreds.
A Real-World Example
Think about testing a new pressure cooker.
- An example test: cook 1 cup of rice once. It works.
- A property test: "the lid never opens while there is pressure". You try it with rice, dal, water, a full pot, a nearly empty pot, many times.
- One day, with a nearly empty pot, the lid opens. You found a case nobody planned for.
Property tests are the "many times, many ways" check.
What You Will Learn
In this lesson, you will learn:
- How to write a property with fast-check.
- How shrinking finds the smallest input that breaks a rule.
- Four kinds of good properties: bounds, sums, round trips and "no change".
- How properties and examples work together.
- How to tell a real property from one that just copies the code.
Your First Property
The Parts of a Property Test
A property test has three parts:
- Generators say what random inputs to make. For example,
fc.integer({ min: 0, max: 100 }). - The rule is a function that returns
truewhen the rule holds. fc.assertruns the rule on many inputs. It fails if any input breaks the rule.
Press ▶ Run tests. Green.
- What happened: the tool made 100 random prices and percents, and the rule held for all of them.
- Now try: open
src/discount.tsand change100 - percentto100 + percent. Run again. Red.
This lab uses a small copy of fast-check.
- Real projects install
fast-checkfrom npm. The code you write is the same. fc.float2is a lab helper for money with 2 decimals, like 12.34. In real fast-check, you can make it withfc.integer(...).map((p) => p / 100).- Runs use a fixed seed, so you get the same inputs every time.
Simple lesson: generators, a rule, and fc.assert. That is a property test.
Shrinking
The Smallest Failing Input
When a property fails, the first failing input is often big and messy, like ₹58,230.76.
Shrinking = the tool tries smaller and simpler inputs, and keeps the smallest one that still fails.
Animation: a failing price of 58230.76 is halved again and again: 29115.38, 909.86, 7.11, 0.45, until it reaches 0.01, the smallest input that still fails.
58230.76
909.86
7.11
0.45
0.01
Each step tries a smaller input. It keeps it only if the rule still fails. smallest!
Shrinking turns a messy failure into the simplest one you can debug.
Press ▶ Run tests.
- The Tests tab: red. Read the message: "Counterexample: [0.01]", then every shrink step.
- Counterexample = an input that breaks the rule.
- Why it fails: the code rounds to whole rupees. ₹0.01 becomes ₹0.
- Fix it: change the last line to
Math.round(price * (100 - percent)) / 100. Run again. Green.
Simple lesson: shrinking hands you the simplest broken case. Start debugging there.
Kinds of Good Properties
Good properties describe the result, without repeating the formula. Here are four kinds that work on most code.
Bounds
Bounds = the result always stays inside a range.
- A discount is between 0 and the price.
- A rating average is between 1 and 5.
Sums and Counts
The parts add up to the whole, and nothing is lost.
Press ▶ Run tests.
- The Tests tab: red. "Counterexample: [0.01, 2]". ₹0.01 split between 2 people loses the paisa.
- Why: this version of
splitBilldrops the extra paise. - A person might never try ₹0.01 for 2 people. The tool tried it for you.
Round Trips
Round trip = going there and back gives the same value. For example, format a number as text, then read it back.
Press ▶ Run tests. Green.
- Now try: change
toFixed(2)totoFixed(1)insrc/money.ts. Run again. - The Tests tab: red, with a small counterexample like ₹0.01.
No Change
Some inputs should change nothing.
- 0% off keeps the price.
- Sorting a list that is already sorted gives the same list.
Do not copy the code into the property.
applyDiscount(p, x) === Math.round(p * (100 - x)) / 100repeats the formula.- If the formula is wrong, the property is wrong in the same way.
- Describe what must be true of the result instead: bounds, sums, round trips, no change.
Simple lesson: a good property says what the result must look like, not how to compute it.
Properties Plus Examples
Properties and examples work best together.
- Examples pin exact answers: "10% off ₹500 is ₹450".
- Properties guard the rules across many inputs, including odd ones.
Press ▶ Run tests, then 🐞 Plant bugs.
- The Bug check tab: one example plus one property catch all five discount bugs.
- Why: the example catches wrong maths. The 0% property catches special cases and rounding.
Where properties help most with AI code:
- Money and rounding.
- Lists: sorting, splitting, merging.
- Text: format, parse, trim.
- Any rule with "never", "always" or "adds up to".
Simple lesson: examples check what you know. Properties find what you did not think of.
TL;DR
- Property = a rule that must hold for every input.
- Generator = says what random inputs to make, like
fc.integer({ min: 0, max: 100 }). fc.assert(fc.property(...))= runs the rule on many random inputs.- Counterexample = an input that breaks the rule.
- Shrinking = finding the smallest input that still breaks it.
Good kinds of property:
- bounds
- sums and counts
- round trips
- no change
- Do not copy the formula into the property.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Copying the formula | "The property is just the code again." | Describe the result: bounds, sums, round trips. | A wrong formula passes its own check. |
| Rule too weak | "It returns a number." | Pick a rule a bug would break. | Every bug still returns a number. |
| Rule too strict | "All shares are equal." | Check what is really always true. | The test fails on correct code. |
| Ignoring the counterexample | "It is a random fluke." | It is a real input that breaks the rule. | The bug ships for that input. |
| Only properties | "Properties replace examples." | Use both. | Exact answers stop being checked. |
Check Your Understanding
Exercises
What to Remember
- A property is a rule for every input. The tool makes the inputs.
- Shrinking gives you the smallest failing case.
- Good properties: bounds, sums and counts, round trips, no change.
- Never copy the formula into the property.
- Use properties and examples together.
Further Reading
- fast-check documentation: the real tool, with every generator.
- Vitest getting started: run property tests with your other tests.
Next: Integration and Contract Tests. A mocked unit test stays green while the real API changes. You will add a contract test that catches it.