mrkeyoor.com_
Sun 20 Sept 11:46 UTC
npmTestingupdated 20 Sept 2026

fast-check review

fast-check 4.9.0 generates inputs for JavaScript and TypeScript tests, checks a rule against each input, and reduces a failure to a smaller counterexample. The useful artifact is the seed and shrink path printed with that counterexample: both can reproduce the run. It also covers stateful models and scheduled promises, two jobs that fixture generators do not attempt. This release introduces chainUntil for shrinkable entity graphs, fixes shared state in entityGraph, and gives stringMatching alternatives equal selection weight. In our package checks, CommonJS require and ESM import both loaded successfully and TypeScript declarations were included.

30.1Mdownloads / wk
Verdict

fast-check 4.9.0 installed in 0.5 seconds and occupied 2 MB in our sandbox, with 0 audit findings, bundled types, and replayable shrinking for failed properties. Add it where the code has a genuine invariant; use ordinary example tests for exact business scenarios and UI states.

We installed it

Lab card: what happened when we installed fast-checkScreenshot of fast-check documentation
Install✓ · 0.5s2 packages on disk · 2 MB
ImportESM import works · require() works · ESM package with exports map
Browser55.4 KBgzipped (162.4 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does fast-check install cleanly?

Yes. In a fresh container with an empty cache, npm install fast-check finished in 0.5s, leaving 2 packages and 2 MB on disk. npm audit reported no known vulnerabilities.

How much does fast-check add to a browser bundle?

55.4 KB gzipped (162.4 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does fast-check work with both ESM and CommonJS?

Yes. Both import 'fast-check' and require('fast-check') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does fast-check include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

fast-check or @fast-check/vitest: which should you use?

@fast-check/vitest: Choose the official Vitest adapter when property cases should use test.prop and the runner's normal reporting. fast-check 4.9.0 installed in 0.5 seconds and occupied 2 MB in our sandbox, with 0 audit findings, bundled types, and replayable shrinking for failed properties.

When should you not use fast-check?

Most checks are fixed UI states or exact request payloads, where named examples explain the requirement more clearly than generated cases

API stability4/5Version 4.9.0 keeps the central arbitrary, property, and assert workflow intact and adds chainUntil as a new combinator. The major 4 migration was still substantial: it removed character helpers, consolidated string APIs, changed date and bigint behavior, and allowed null-prototype records. The exports map served both require() and ESM import in our check, but a major upgrade needs the published migration guide and a full test pass.
Docs5/5The official site documents each arbitrary family, runner settings, shrinking reports, seeds and paths, custom examples, model-based tests, and promise scheduling. Its quick start demonstrates an actual shrunken failure rather than stopping at generated sample data. Dedicated migration pages map removed version 3 helpers to version 4 forms, which matters because many older search results still use fc.char and fc.stringOf.
Maintenance5/5GitHub recorded a repository push on 2026-08-26, and release 4.9.0 was published on 2026-07-08. That release includes the new chainUntil API, two user-facing correctness fixes, generator performance work, publication hardening, and expanded benchmarks. GitHub's 77 open count combines issues and pull requests, so it describes the active work queue rather than a true issue total.
Ecosystem4/5npm reported 35,918,716 downloads during the week ending 2026-08-25, and GitHub showed 5,118 stars at research time. The base package works with any runner that treats a thrown assertion as failure, while official adapters add property syntax for Vitest and Jest. The specialized concepts, especially shrinking, command models, and scheduled promises, make its learning surface larger than a fixture generator's.

Discussed on

  1. hnFastcheck: Property based testing for JavaScript and TypeScript61 points
  2. hnfast-check 1.2.0 adds support for chained generators5 points
  3. hnFast-check 1.0.1 released – feedbacks welcome5 points
  4. hnProperty Based for JavaScript: Fast-Check – Official Website4 points
  5. hnIntroduction to fast-check4 points

Use it if

  • A parser, serializer, sorter, validator, or numeric routine has a rule that should hold across a wide input range
  • You want a failing random case reduced to a small input and replayable from the seed plus shrink path
  • A cache, queue, reducer, or other stateful object can be compared with a simpler command model
  • Promise ordering may hide a race and you can route the relevant asynchronous work through fc.scheduler
Skip it if

Setup reality

We installed fast-check 4.9.0 without a cache on Node 22. npm finished in 0.5 seconds, placed 2 packages on disk, and used 2 MB in total. The package declares 1 direct dependency and 0 peers; its unpacked payload is 1,448 KB. npm audit returned 0 known vulnerabilities. It ships TypeScript declarations, uses an ESM package layout with an exports map, and loaded through both require() and ESM import in our sandbox.

There are no credentials or config files. Install it as a development dependency and call fc.assert from the test runner you already use. The official Vitest and Jest adapters are separate packages for teams that want test.prop. Version 4 requires Node 12.17 or newer and its README lists TypeScript 5.0 as the supported floor for typed use.

The runner tries 100 generated cases by default. A predicate that writes to a database can therefore perform 100 writes before shrinking starts. Await fc.assert when using asyncProperty, reset mutable fixtures for every candidate, and set numRuns from the actual cost of the property. Heavy use of filter or fc.pre can exhaust the allowed skips; generating a valid range directly is usually cheaper.

A failure report carries the generated value, seed, and shrink path. Store the seed and path with the bug, replay them in fc.assert, then add a fixed regression example after the repair. Our all-exports browser build measured 162.4 KB minified and 55.4 KB gzipped. That is a real frontend cost, so test-only use should stay out of production bundles.

Patterns

Check a serializer round trip check-round-trip

import fc from 'fast-check';

fc.assert(
  fc.property(fc.jsonValue(), (value) => {
    return JSON.parse(JSON.stringify(value)) !== undefined;
  }),
);

fc.assert throws when the predicate returns false or throws. The runner then reduces the generated JSON value before reporting it.

Await storage round trips test-async-property

await fc.assert(
  fc.asyncProperty(fc.uuid(), fc.jsonValue(), async (id, value) => {
    await store.set(id, value);
    expect(await store.get(id)).toEqual(value);
  }),
  { numRuns: 40 },
);

The outer fc.assert returns a promise for asyncProperty. Without await, a test runner can finish the case before generation or shrinking completes.

Generate typed account records compose-record

const accountArb = fc.record(
  {
    id: fc.uuid(),
    email: fc.emailAddress(),
    seats: fc.integer({ min: 1, max: 500 }),
  },
  { noNullPrototype: true },
);

Version 4 record values may use a null prototype. noNullPrototype produces ordinary objects for code that calls inherited Object methods.

Select text units in version 4 generate-v4-text

const labelArb = fc.string({ minLength: 1, maxLength: 60 });
const graphemeArb = fc.string({ unit: 'grapheme', maxLength: 20 });
const hexArb = fc.string({
  unit: fc.constantFrom('0', '1', 'a', 'b', 'c', 'd', 'e', 'f'),
});

Version 4 removed fc.char and fc.stringOf. Pass a built-in unit or an arbitrary as the unit option to fc.string.

Replay one reported counterexample replay-failure

fc.assert(propertyUnderTest, {
  seed: 1527422598337,
  path: '3:0:1',
  endOnFailure: true,
});

seed and path must come from the failure report. Together they jump to the same shrunken case; endOnFailure prevents another shrink pass.

Put regressions before random cases run-known-examples

fc.assert(fc.property(fc.string(), normalizeTwice), {
  examples: [[''], ['\u0000'], ['  spaced  ']],
  numRuns: 200,
});

Each item in examples is the argument array for one property call. Those cases run before the 200 generated candidates.

Generate an array with a valid index derive-dependent-input

const arrayAndIndexArb = fc
  .array(fc.integer(), { minLength: 1 })
  .chain((items) =>
    fc.tuple(
      fc.constant(items),
      fc.integer({ min: 0, max: items.length - 1 }),
    ),
  );

chain lets the index bounds depend on the generated array and preserves shrinking. A broad index followed by filter would discard invalid candidates.

Exclude zero divisors apply-precondition

fc.assert(
  fc.property(fc.integer(), fc.integer(), (left, right) => {
    fc.pre(right !== 0);
    return Math.abs(left % right) < Math.abs(right);
  }),
);

fc.pre discards a candidate rather than passing it. Too many discarded candidates make the property fail with an exhaustion error.

Run commands against a model test-command-model

fc.assert(
  fc.property(commandArb, (commands) => {
    fc.modelRun(
      () => ({ model: [], real: new Stack() }),
      commands,
    );
  }),
);

Every generated command needs check and run methods. modelRun shrinks the command sequence that causes the real stack to diverge from the array model.

Explore promise completion orders schedule-promises

await fc.assert(
  fc.asyncProperty(fc.scheduler(), async (scheduler) => {
    const first = scheduler.schedule(loadUser('a'));
    const second = scheduler.schedule(loadUser('b'));
    await scheduler.waitIdle();
    await Promise.all([first, second]);
  }),
);

fc.scheduler controls only promises scheduled through it. Calls that bypass scheduler.schedule retain their runtime completion order.

Set project-wide run limits configure-runner

fc.configureGlobal({
  numRuns: process.env.CI ? 500 : 50,
  interruptAfterTimeLimit: 30_000,
  markInterruptAsFailure: true,
});

configureGlobal affects later properties in the same process. Load it once from the test runner's setup file before test modules execute.

Sample a URL arbitrary inspect-generator

const urls = fc.sample(
  fc.webUrl({ validSchemes: ['https'] }),
  { seed: 42, numRuns: 10 },
);
console.log(urls);

fc.sample produces 10 inspectable values here. It does not run a property, report a failure, or shrink any sample.

Alternatives

PackageRegistryPick it when
@fast-check/vitestnpmChoose the official Vitest adapter when property cases should use test.prop and the runner's normal reporting.
@faker-js/fakernpmChoose it for plausible fixture records when shrinking and systematic edge cases are outside the job.
jsverifynpmKeep it in an existing QuickCheck-style JavaScript suite when a migration would buy little.

More testing guides

pytest · chai · vitest · jsdom · playwright · coverage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.