fast-check: property-based testing for JavaScript and TypeScript
Property based testing framework for JavaScript (like QuickCheck) written in TypeScript
At a glance
- What is it?
- fast-check generates inputs from properties instead of hand-written cases, and shrinks any counterexample it finds. It fits teams already running Jest or Vitest who want generated coverage, not a replacement test runner.
- Who is it for?
- Adopt fast-check if you already run Jest, Vitest, Mocha, Ava, Jasmine or Tape and have pure functions or state machines whose invariants you can state in words. Skip it if your logic is mostly I/O, timing or third-party behaviour, where generated inputs mostly reproduce what integration tests already cover.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem fast-check solves: examples do not cover input space
A hand-written unit test asserts one input and one output. That is fine until the bug lives in a boundary you did not think of: an empty string, a string containing its own separator, a negative zero, an array where two elements are equal. fast-check inverts the workflow. You state a property, meaning a statement of the form "for all x, y such that a precondition holds, a predicate is true", and the library generates the inputs, runs the predicate, and reports a counterexample when it fails. The README frames the whole project this way: property based testing frameworks check the truthfulness of properties.
The intended audience is JavaScript and TypeScript developers who already have a test runner. The README shows integration examples for ava, jasmine, jest, mocha and tape, and the repository ships an examples directory with numbered folders including 001-simple, 002-recursive, 003-misc, 004-stateMachine and 005-race. That list is a fair summary of where the approach pays off: recursive data structures, state machines, and concurrent or racing behaviour. It is a library, not a runner, so it slots into whatever you already use.
Arbitraries, properties and shrinking: the actual mechanism
Two types carry the design. An arbitrary describes how to generate values of some type, and a property combines arbitraries with a predicate. fc.string() is an arbitrary; fc.property(fc.string(), (text) => contains(text, text)) is a property. fc.assert runs it and throws on failure.
Generation is biased by default. The README states that fast-check generates both small and large values, so counterexamples tend to be small without you tuning a size parameter. That matters more than it sounds: a failing case of three characters is readable, a failing case of a 4 KB random string is not.
Shrinking is the second half. When a predicate fails, fast-check searches for a smaller input that still fails and reports that. The README's failure output shows the shape: "Property failed after 1 tests (seed: 1527422598337, path: 0:0)", then "Shrunk 1 time(s)", then the failing values. The seed and path are the reproduction handle; the shrunk values are the diagnosis.
Extending an arbitrary keeps the shrinker, which is the part most generators get wrong. The README argues this explicitly: map derives a new arbitrary from an existing one while keeping shrink, and chain binds the output of one arbitrary as input to another while keeping shrink working. Some frameworks, it notes, ask you to supply both directions of a mapping to preserve shrinking. Preconditions are handled with fc.pre(...) inside the predicate, and fc.gen() lets you generate values from inside a predicate when you want to move from fixed fake data to generated data gradually.
Installing fast-check and writing a first property
Install it as a development dependency with the package manager you already use. The README gives three equivalent commands: pnpm add -D fast-check, yarn add fast-check --dev, or npm install fast-check --save-dev.
npm install fast-check --save-devThe README's Mocha example is the clearest first read, because it shows both the code under test and the property. It imports the whole module as fc, defines a function that returns whether a text contains a pattern, and asserts that a string always contains itself.
import * as fc from 'fast-check';
const contains = (text, pattern) => text.indexOf(pattern) >= 0;
await fc.assert(fc.property(fc.string(), (text) => contains(text, text)));A passing run prints nothing beyond your runner's normal output. To see the failure path, break the implementation deliberately, for example by making contains always return false, and run it again. The README's sample output looks like this:
Error: Property failed after 1 tests (seed: 1527422598337, path: 0:0): ["","",""]
Shrunk 1 time(s)
Got error: Property failed by returning falseKeep that seed. It is the input to re-running the same failing sequence deterministically. The README points to a verbose mode for the full list of failing values encountered during a run, which is the setting to reach for when the shrunk case alone does not explain the bug.
Where fast-check is the wrong tool
Generated inputs are only as good as the property you can write. If the correct behaviour is "this HTTP call returns 200 and the row appears in the database", there is no useful for-all statement, and fast-check adds ceremony without adding coverage. Integration and end-to-end tests remain the right instrument there.
Timing is a second boundary. The recent v4.10.1 release is titled "Fix fake-timer compatibility in timeout and interrupt plugins", which is a reminder that the interaction between generated async work and fake timers has needed patching. If your code is dominated by timers, retries and scheduling, expect to spend time on the harness rather than on the property.
Third, shrinking is a search, not a guarantee. The README's own example reports "Shrunk 1 time(s)" for a trivial case, but nothing in the documentation promises a minimal counterexample in general. For a predicate with a narrow precondition, most generated values may be discarded, and the run gets slower without getting more informative.
Finally, an async API surface has a cost. The README's own examples await fc.assert, including the Mocha block, and the project ships a 005-race example. That is a signal that concurrent properties are supported and also that they are where the sharp edges are.
fast-check versus Vitest and Jest
These are not competing choices, and the search phrasing that pits them against each other is misleading. Vitest and Jest are test runners: they discover files, execute test cases, and report pass or fail. fast-check is a generator and a shrinker that runs inside a test case. The README lists integration examples for both jest and jasmine, and the repository has a fast-check/vitest relationship in its own tooling: the monorepo's test script is vitest and the workspace includes a vitest.config.mjs.
The real alternative is a fuzzer or a hand-rolled random-input loop. A loop that calls Math.random and retries on failure gives you no shrinking and no seed, so a failure you cannot reproduce is a failure you cannot fix. fast-check's difference is the shrink step plus the seed and path in the report. If you already have Jest or Vitest, the practical decision is not which runner to use but which functions in your codebase have a statable invariant worth generating inputs for.
Maintenance, releases and the MIT licence
The repository is not archived and the last push was on 2026-09-22. Releases are frequent and small: v4.10.0 on 2026-09-11 introduced a new plugin API and deprecations ahead of v5, v4.10.1 on 2026-09-15 fixed fake-timer compatibility, and v4.10.2 followed on 2026-09-19. The changelog is managed with Changesets, visible in the .changeset directory and the bump and changelog scripts in the root package.json.
That cadence has an upgrade cost worth naming. A deprecation wave ahead of a major version means the plugin API you write today may need editing before v5. The version number in your package.json is the thing to pin deliberately. The monorepo itself requires pnpm 12.5.1 per the devEngines block, but that governs contributing to fast-check, not consuming it.
The licence is MIT, which permits commercial and closed-source use. That is a statement about the licence text, not legal advice; check it against your own distribution requirements.
Editorial conclusion
Adopt fast-check if you already run Jest, Vitest, Mocha, Ava, Jasmine or Tape and have pure functions or state machines whose invariants you can state in words. Skip it if your logic is mostly I/O, timing or third-party behaviour, where generated inputs mostly reproduce what integration tests already cover. Before committing, run one property against a function you know is correct, then deliberately break it and read the shrunk counterexample and the seed it prints, because that report is what you will live with on every future failure.
Frequently asked questions
What is fast-check?
fast-check is a property-based testing framework for JavaScript and TypeScript, written in TypeScript and modelled on QuickCheck. Instead of asserting single examples, you state properties and it generates inputs to try to falsify them.
How does property-based testing work?
You combine arbitraries, which describe how to generate values, with a predicate using fc.property, and run it with fc.assert. On failure fast-check shrinks the input to a smaller failing case and reports a seed and path for reproduction.
What is the fast-check npm package?
It is the published package for the library, installed as a development dependency with npm install fast-check --save-dev, or the equivalent pnpm or yarn command from the README.
Is fast-check a replacement for Vitest?
No. Vitest is a test runner that discovers and executes tests; fast-check is a library that runs inside a test case. The README lists integration examples for jest, ava, jasmine, mocha and tape.
Is fast-check a replacement for Jest?
No. Jest is a test runner, while fast-check generates and shrinks inputs inside a test case. The README lists a Jest integration example alongside ava, jasmine, mocha and tape.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/dubzzz-fast-check)