fast-check
$
npx mdskill add TerminalSkills/skills/fast-checkAutomatically find edge-case bugs using property-based testing in JavaScript/TypeScript
- Tests functions with thousands of random inputs to uncover hidden edge cases
- Integrates with Vitest, Jest, Mocha, or runs standalone in JavaScript/TypeScript projects
- Generates and shrinks inputs to identify minimal failing test cases
- Reports reproducible failures and confirms properties hold for all valid inputs
SKILL.md
.github/skills/fast-checkView on GitHub ↗
---
name: fast-check
description: >-
Find edge-case bugs with property-based testing using fast-check — generate
thousands of random inputs automatically. Use when someone asks to "find
edge cases", "fast-check", "property-based testing", "fuzz testing in
TypeScript", "generate random test data", "QuickCheck for JavaScript",
or "test with random inputs". Covers property definitions, arbitraries,
shrinking, model-based testing, and integration with Vitest/Jest.
license: Apache-2.0
compatibility: "TypeScript/JavaScript. Vitest, Jest, Mocha, or standalone."
metadata:
author: terminal-skills
version: "1.0.0"
category: development
tags: ["testing", "property-based", "fast-check", "fuzzing", "edge-cases"]
---
# fast-check
## Overview
fast-check is a property-based testing framework — instead of writing specific test cases, you describe properties that should always hold, and fast-check generates thousands of random inputs to try to break them. When it finds a failing input, it automatically shrinks it to the minimal reproducing case. Catches edge cases you'd never think to test: empty strings, negative numbers, Unicode, massive arrays, boundary values.
## When to Use
- Functions with many possible inputs (parsers, validators, serializers)
- Finding edge cases in business logic (pricing, permissions, date handling)
- Testing serialization/deserialization roundtrips (encode → decode = original)
- Verifying mathematical properties (commutativity, associativity)
- Replacing hand-written test cases with generated ones
## Instructions
### Setup
```bash
npm install -D fast-check
```
### Basic Properties
```typescript
// math.test.ts — Property-based testing for math functions
import fc from "fast-check";
import { describe, it, expect } from "vitest";
describe("sort", () => {
it("should produce an array of same length", () => {
fc.assert(
fc.property(fc.array(fc.integer()), (arr) => {
const sorted = [...arr].sort((a, b) => a - b);
return sorted.length === arr.length;
})
);
});
it("should produce ordered output", () => {
fc.assert(
fc.property(fc.array(fc.integer()), (arr) => {
const sorted = [...arr].sort((a, b) => a - b);
for (let i = 1; i < sorted.length; i++) {
expect(sorted[i]).toBeGreaterThanOrEqual(sorted[i - 1]);
}
})
);
});
it("should contain the same elements", () => {
fc.assert(
fc.property(fc.array(fc.integer()), (arr) => {
const sorted = [...arr].sort((a, b) => a - b);
expect(sorted).toEqual(expect.arrayContaining(arr));
expect(arr).toEqual(expect.arrayContaining(sorted));
})
);
});
});
```
### Roundtrip Testing
```typescript
// serialization.test.ts — Encode/decode roundtrips
import fc from "fast-check";
import { encode, decode } from "./my-codec";
it("decode(encode(x)) === x for any string", () => {
fc.assert(
fc.property(fc.string(), (original) => {
const encoded = encode(original);
const decoded = decode(encoded);
expect(decoded).toEqual(original);
})
);
});
it("JSON roundtrip preserves data", () => {
fc.assert(
fc.property(fc.jsonValue(), (value) => {
const json = JSON.stringify(value);
const parsed = JSON.parse(json);
expect(parsed).toEqual(value);
})
);
});
```
### Custom Arbitraries
```typescript
// business.test.ts — Custom data generators for domain objects
import fc from "fast-check";
// Generate realistic user objects
const userArbitrary = fc.record({
id: fc.uuid(),
name: fc.string({ minLength: 1, maxLength: 100 }),
email: fc.emailAddress(),
age: fc.integer({ min: 13, max: 120 }),
role: fc.constantFrom("admin", "user", "viewer"),
createdAt: fc.date({ min: new Date("2020-01-01"), max: new Date() }),
});
// Generate realistic money amounts
const moneyArbitrary = fc.record({
amount: fc.integer({ min: 0, max: 1_000_000 }), // Cents
currency: fc.constantFrom("USD", "EUR", "GBP"),
});
it("discount should never result in negative price", () => {
fc.assert(
fc.property(
moneyArbitrary,
fc.integer({ min: 0, max: 100 }), // Discount percentage
(price, discountPercent) => {
const discounted = applyDiscount(price, discountPercent);
expect(discounted.amount).toBeGreaterThanOrEqual(0);
}
)
);
});
it("total should equal sum of line items", () => {
fc.assert(
fc.property(
fc.array(moneyArbitrary, { minLength: 1, maxLength: 50 }),
(items) => {
const total = calculateTotal(items);
const expected = items.reduce((sum, item) => sum + item.amount, 0);
expect(total).toBe(expected);
}
)
);
});
```
### Shrinking (Automatic Minimal Reproduction)
```typescript
// When a property fails, fast-check automatically finds the SMALLEST failing input
it("handles edge cases in URL parsing", () => {
fc.assert(
fc.property(fc.webUrl(), (url) => {
const parsed = parseUrl(url);
expect(parsed.protocol).toBeTruthy();
// If this fails on a complex URL, fast-check shrinks it to
// the simplest URL that still fails, e.g., "http://a"
})
);
});
```
## Examples
### Example 1: Test a parser for edge cases
**User prompt:** "My CSV parser breaks on weird inputs. Find all the edge cases."
The agent will write properties like "parse(serialize(data)) === data", generate random CSV data with edge cases (commas in fields, newlines, empty cells, Unicode), and identify failing inputs.
### Example 2: Verify pricing logic
**User prompt:** "Test our discount calculation to make sure it never produces negative prices or rounding errors."
The agent will generate random prices, discount percentages, and coupon combinations, then verify invariants (non-negative, correct rounding, order doesn't matter).
## Guidelines
- **Think in properties, not examples** — "sorted output is ordered" not "sort([3,1,2]) = [1,2,3]"
- **Roundtrip is the easiest property** — `decode(encode(x)) === x`
- **`fc.assert` runs 100 iterations by default** — increase with `{ numRuns: 1000 }`
- **Shrinking is automatic** — failed inputs are minimized to the simplest case
- **Built-in arbitraries cover most needs** — `string`, `integer`, `array`, `record`, `date`, `uuid`, `emailAddress`
- **`constantFrom` for enums** — `fc.constantFrom("a", "b", "c")`
- **`fc.pre(condition)` for preconditions** — skip inputs that don't meet criteria
- **Combine with example-based tests** — properties find unknowns, examples verify knowns
- **Seed for reproducibility** — failed runs print a seed; re-run with same seed to reproduce