Combining & Transforming Arbitraries
Relevant source files
The following files were used as context for generating this wiki page:- packages/fast-check/src/check/arbitrary/definition/Arbitrary.ts
- packages/fast-check/src/arbitrary/oneof.ts
- packages/fast-check/test/e2e/arbitraries/OneOfArbitrary.spec.ts
- packages/fast-check/MIGRATION_2.X_TO_3.X.md
- website/blog/2025-03-10-whats-new-in-fast-check-4-0-0/index.md
- website/blog/2025-12-15-whats-new-in-fast-check-4-4-0/index.md
In fast-check, an "Arbitrary" is a component responsible for generating random values and shrinking them towards a minimal counter-example when a test fails. While the library provides a rich set of built-in arbitraries for primitive types (like fc.nat(), fc.string()), the real power for testing complex applications comes from the ability to combine and transform these basic building blocks into generators for any custom data structure.
This document outlines the core mechanisms for creating complex and custom arbitraries: transformation methods available on the Arbitrary class and combiner functions that assemble multiple arbitraries into a new, more complex one. These tools are fundamental for modeling the data required by your properties.
The Arbitrary<T> Class: A Foundation for Transformation
Every arbitrary in fast-check is an instance of the Arbitrary<T> class, where T is the type of the generated value. This class provides several key methods for transforming its output into a new arbitrary.
Sources: packages/fast-check/src/check/arbitrary/definition/Arbitrary.ts
Transformation Methods
| Method | Description |
|---|---|
.map(mapper, unmapper?) | Transforms a generated value of type T into a new value of type U, creating an Arbitrary<U>. An optional unmapper function can be provided to aid in shrinking. |
.filter(predicate) | Creates a new arbitrary that only produces values from the original arbitrary that satisfy the predicate function. |
.chain(chainer) | A powerful but advanced method that generates a value from the first arbitrary and uses it to create a second arbitrary, from which the final value is generated. |
Sources: packages/fast-check/src/check/arbitrary/definition/Arbitrary.ts:16-29
.map(mapper)
The .map() method is the most common way to transform the output of an arbitrary. It takes a generated value and applies a function to it.
// Creates an arbitrary for even numbers by mapping over a natural number arbitrary.
const evenNumberArbitrary = fc.nat().map((n) => n * 2);
The following diagram illustrates the data flow of the .map() method.
For shrinking to work effectively on mapped values, fast-check needs to be able to reverse the mapping. If a simple unmapper function is not provided, the shrinker may be less efficient as it has to search for a shrinkable base value that produces the target mapped value.
Sources: packages/fast-check/src/check/arbitrary/definition/Arbitrary.ts:16-18
.filter(predicate)
The .filter() method is used to constrain the values an arbitrary can produce. It discards any generated value that does not satisfy the provided predicate function.
// Creates an arbitrary for odd numbers by filtering even ones out.
const oddNumberArbitrary = fc.nat().filter((n) => n % 2 !== 0);
Warning: Overusing .filter() or using a predicate that is too restrictive can severely impact performance. If fast-check fails to generate a valid value after a certain number of attempts, it will throw an error. It is often better to build a generator that produces valid data by construction using .map() or .chain() instead of filtering invalid data out.
Sources: packages/fast-check/src/check/arbitrary/definition/Arbitrary.ts:22-24
Combining Arbitraries
fast-check provides several functions to combine multiple arbitraries into a single one that generates more complex data structures like tuples, objects, or values from a heterogeneous set.
fc.oneof(...arbs)
The oneof function creates an arbitrary that randomly picks one of the provided arbitraries to generate a value. This is useful for generating values that can be one of several types or shapes.
// An arbitrary that can generate a string, a number, or a boolean.
const primitiveArb = fc.oneof(fc.string(), fc.nat(), fc.boolean());
The selection process is illustrated below.
oneof can also be used for weighted selection by wrapping arbitraries in a {weight, arbitrary} object. It also supports a depthSize parameter, which is important for controlling the depth of recursive structures to prevent infinite recursion.
Sources: packages/fast-check/src/arbitrary/oneof.ts:120-129, packages/fast-check/test/e2e/arbitraries/OneOfArbitrary.spec.ts:25-30
Evolution from fc.frequency
In versions prior to 3.x, weighted selection was handled by a separate fc.frequency function. This has been deprecated and its functionality merged into fc.oneof.
Old Syntax (<3.x) | New Syntax (>=3.x) |
|---|---|
fc.frequency({weight: 1, arbitrary: fc.nat()}, {weight: 2, arbitrary: fc.string()}) | fc.oneof({weight: 1, arbitrary: fc.nat()}, {weight: 2, arbitrary: fc.string()}) |
Sources: packages/fast-check/MIGRATION_2.X_TO_3.X.md:90-91
Other Combiners
fast-check includes other essential combiners for building common data structures.
| Combiner | Description |
|---|---|
fc.tuple(...arbs) | Generates a fixed-length array (tuple) where each element is generated by the corresponding arbitrary in the input list. |
fc.record(template) | Generates an object whose structure is defined by the template object, where each key is mapped to an arbitrary. |
fc.dictionary(keyArb, valueArb) | Generates an object with keys from keyArb and values from valueArb. Since v4.4.0, it supports the full range of PropertyKey (string, number, symbol) for keys. |
fc.map(keyArb, valueArb) | Generates a Map instance with keys and values from the provided arbitraries. Introduced in v4.4.0. |
fc.set(valueArb) | Generates a Set instance with unique values from the provided arbitrary. Introduced in v4.4.0. |
fc.letrec(builder) | A powerful combiner for generating recursive data structures like trees or linked lists. |
Sources: website/blog/2025-03-10-whats-new-in-fast-check-4-0-0/index.md:36-41, website/blog/2025-12-15-whats-new-in-fast-check-4-4-0/index.md:12-42
Conclusion
Combining and transforming arbitraries are core concepts in fast-check that enable property-based testing for complex, real-world applications. By starting with simple, built-in arbitraries and applying transformations like .map() or using combiners like fc.oneof, fc.tuple, and fc.record, developers can construct precise and powerful generators for any data model. This declarative approach to data generation allows tests to focus on the properties of the system, while fast-check handles the work of creating a diverse range of inputs to find edge cases.