edgegoat Guides
Property based testing and curated edge cases in JavaScript
fast-check generates thousands of random inputs and shrinks failures to a minimal example. edgegoat hands you the few hundred inputs that already broke real code, with names and reasons. They answer different questions and work well together.
What each approach is good at
| fast-check | edgegoat | big-list-of-naughty-strings | |
|---|---|---|---|
| Inputs | Random, generated from arbitraries | Curated, deterministic | Curated raw strings |
| Finds | Unknown bugs, with shrinking | Known bug classes, every run | String handling bugs |
| Explains why | No | Yes, one line per case | Comments by section |
| Types | Any | Dates, zones, strings, numbers, URLs, paths, JSON, arrays | Strings |
| DST and New Year instants | Rarely hit by chance | Computed per zone | No |
The bug random inputs rarely find
A uniformly random instant falls inside a given one hour window about once in 8,760 tries, so a property test over random dates almost never lands on the hour where clocks jump. dates({ tags: ['dst'] }) hits it on every run, in every zone you choose. The same goes for a surrogate pair exactly at a cut boundary or a number between -1 and 0.
Seed fast-check with edgegoat values
import fc from 'fast-check';
import { test } from 'vitest';
import { strings } from 'edgegoat';
const tricky = fc.constantFrom(...strings().map((c) => c.value));
const text = fc.oneof(tricky, fc.string({ unit: 'grapheme' }));
test('slugify is idempotent', () => {
fc.assert(fc.property(text, (s) => slugify(slugify(s)) === slugify(s)));
});fast-check mixes the curated values with random ones and still shrinks failures. unit: 'grapheme' needs fast-check 3.22 or later.
Or keep the labels with check()
The same property, without a property testing library, reports failures by name and reason:
import { expect, test } from 'vitest';
import { check, strings } from 'edgegoat';
test('slugify is idempotent on every edge case', () => {
const failures = check(slugify, strings(), (out) => slugify(out) === out);
expect(failures.map((f) => `${f.label}: ${f.why}`)).toEqual([]);
});FAQ
Instead of writing examples, you state a property that must hold for all inputs (for example "decode(encode(x)) equals x") and a library such as fast-check checks it against many generated inputs, shrinking any failure to a minimal case.
Both. Use fast-check to explore inputs you did not think of, and edgegoat to make sure the inputs that are known to break code are always tested, with readable names in the test report.
No. Every call returns the same cases, so failures are reproducible. DST instants are computed from the runtime tz database, so they are deterministic for a given Node.js version.
The big list is a large list of strings for fuzzing. edgegoat is smaller, explains every case, is filterable by tags and also covers dates, time zones, numbers, URLs, paths, JSON and arrays.