Preconditions with `fc.pre`
Relevant source files
The following files were used as context for generating this wiki page:- packages/fast-check/src/check/precondition/Pre.ts
- packages/fast-check/src/check/precondition/PreconditionFailure.ts
- packages/fast-check/test/e2e/PreConditionChecks.spec.ts
- packages/fast-check/test/unit/check/precondition/Pre.spec.ts
- website/blog/2024-03-04-whats-new-in-fast-check-3-16-0/index.md
- packages/ava/CHANGELOG_1.X.md
The fc.pre function is a core feature in fast-check that allows testers to specify conditions that must be met by generated values before a property is executed. It provides a mechanism to filter out irrelevant or invalid test cases on the fly, ensuring that the property's logic is only tested against data that satisfies specific constraints. This is particularly useful when it's easier to define a broad generator (arbitrary) and then filter its output, rather than creating a highly complex and specific arbitrary from scratch.
When a precondition specified by fc.pre is not met, fast-check discards the current set of generated values and attempts to generate a new set. This is distinct from a property failure; it is treated as a skipped test case. If the runner fails to find a valid set of values after a certain number of attempts, it will fail the overall test, indicating that the defined arbitraries and preconditions are too restrictive.
Core Components
The precondition system is built upon two main components: the pre function and the PreconditionFailure exception.
Sources: packages/fast-check/src/check/precondition/Pre.ts, packages/fast-check/src/check/precondition/PreconditionFailure.ts
The pre function
The pre function is the public-facing API for setting preconditions. It accepts a single boolean argument. If the condition is true, the function returns, and the test continues. If the condition is false, it throws a PreconditionFailure exception.
Since version 3.16.0, fc.pre is typed as a TypeScript "assertion function" (asserts condition), which provides type-narrowing benefits within the scope of the property.
Sources: packages/fast-check/src/check/precondition/Pre.ts:11-16, website/blog/2024-03-04-whats-new-in-fast-check-3-16-0/index.md:10-14
The following diagram illustrates the simple logic of the pre function.
PreconditionFailure Class
PreconditionFailure is a custom error class used internally to signal that a precondition has not been met. This allows the fast-check runner to distinguish between a genuine test failure and a discarded test case.
A key feature is the static method isFailure(err: unknown), which reliably checks if an error object is a PreconditionFailure. This check is robust even if multiple versions of fast-check are present in the same project, as it uses a well-known symbol (preconditionFailureSymbol) for identification instead of instanceof.
The diagram below shows the structure of the PreconditionFailure class.
Execution Flow
The fast-check runner has specific logic to handle PreconditionFailure exceptions thrown by fc.pre.
- The property function is executed with values generated by the arbitraries.
fc.preis called within the property.- If the precondition is met, the test execution proceeds to the assertions.
- If the precondition fails,
fc.prethrows aPreconditionFailure. - The runner catches this specific exception, discards the current values, and retries with a new set of values.
- If the runner cannot generate a valid value after a configured number of attempts, it will throw an error, failing the test run.
The following sequence diagram demonstrates the interaction between a property test and the fast-check runner when using fc.pre.
Runner Behavior Summary
| Scenario | Runner Action | Result |
|---|---|---|
fc.pre(true) is called | Continues execution of the property. | Test continues. |
fc.pre(false) is called | Catches PreconditionFailure, discards values. | Test run is skipped, new values are generated. |
Too many fc.pre(false) calls | Stops generating new values. | Test fails with an error message. |
Sources: packages/fast-check/test/e2e/PreConditionChecks.spec.ts:11-47
Type Safety with Assertion Functions
Starting with fast-check v3.16.0, fc.pre leverages TypeScript's assertion functions. This improves type safety by allowing the TypeScript compiler to narrow types after a successful precondition check.
For example, if you have a value that could be T | undefined, calling fc.pre to ensure it's not undefined will correctly narrow the type to just T for the remainder of the property's scope.
Example of Type Narrowing:
// Before fast-check v3.16.0
fc.property(fc.array(fc.nat()), (arr) => {
const item = arr.find(n => n > 10);
fc.pre(item !== undefined);
// Type of 'item' is still 'number | undefined'
// A non-null assertion is needed: item!
});
// With fast-check v3.16.0+
fc.property(fc.array(fc.nat()), (arr) => {
const item = arr.find(n => n > 10);
fc.pre(item !== undefined);
// Type of 'item' is correctly narrowed to 'number'
});
Sources: website/blog/2024-03-04-whats-new-in-fast-check-3-16-0/index.md:16-22
Usage in Integrations
The precondition failure mechanism is a fundamental part of fast-check and is supported in official integrations like @fast-check/ava. The changelog for this package explicitly mentions adding and properly supporting fc.pre, allowing for "unsuccessful plans on unmatched preconditions." This highlights the importance of the feature across the fast-check ecosystem.
Sources: packages/ava/CHANGELOG_1.X.md:14-17
Summary
fc.pre is an essential tool for writing robust and focused property-based tests. It provides a clean and declarative way to filter out unsuitable test data without complicating arbitrary definitions. By distinguishing between a precondition failure and a property failure, it gives clear feedback to the developer: a property failure indicates a bug in the code under test, while a precondition-related failure points to a problem with the test's data generation strategy. The integration of TypeScript assertion functions further enhances its utility by improving type safety and developer experience.