From 3.x to 4.x
Simple migration guide to fast-check v4 starting from fast-check v3
Changes in minimal requirements
Name | New requirement | Previous requirement |
---|---|---|
Node | ≥10.5.0 | ≥8 |
ECMAScript specification | ES2020 | ES2017 |
TypeScript (optional) | ≥5.0 | ≥4.1 |
Related pull requests: #5577, #5605, #5617
Update to latest v3.x
Version 4 of fast-check introduces significant changes as part of its major release, including breaking changes. However, many of these changes can be addressed while still using the latest minor release of version 3.
To ensure a smoother migration to version 4, we recommend first upgrading to the latest minor release of version 3. Then, review and address the following deprecation notices to align your codebase with supported patterns.
Changes on date
In version 4, the date
arbitrary will generate any Date
instances by default, including Invalid Date. If your code cannot handle invalid dates, you should add the noInvalidDate: true
constraint to the configuration of your date builder to exclude such values.
-fc.date();
+fc.date({ noInvalidDate: true });
Related pull requests: #5589
Changes on dictionary
In version 4, the default behavior of dictionary
will be updated to generate objects that may have null prototypes by default. As a result, unless configured otherwise, dictionary
can produce both instances inheriting from the usual Object
prototype and instances with no prototype.
If your code requires all generated objects to inherit from the usual Object
prototype, you can set the noNullPrototype
constraint to true
(used to be defaulted to true
in version 3). This option was introduced in version 3.13.0 and can be applied as follows:
fc.dictionary(fc.string(), fc.string(), {
noNullPrototype: true,
// other contraints (if any)...
});
Related pull requests: #5609
Changes on record
In earlier versions, the record
arbitrary included a flag named withDeletedKeys
. Starting with version 2.11.0, this flag was deprecated and replaced by a new flag called requiredKeys
. In version 4.0.0, the deprecated withDeletedKeys
flag has been removed entirely.
To migrate, update your usage of the record
arbitrary as follows:
fc.record(recordModel, {
- withDeletedKeys: true,
+ requiredKeys: [],
});
fc.record(recordModel, {
- withDeletedKeys: false,
});
Additionally, the default behavior of record
has been updated in version 4 to generate objects that may have null prototypes by default. This means that, unless configured otherwise, record
will produce instances inheriting from the usual Object
prototype as well as instances with no prototype.
If your code requires all generated objects to have the usual Object
prototype, you can configure the noNullPrototype
constraint to true
. It has been introduced in version 3.13.0 and can be set as follow:
fc.record(recordModel, {
noNullPrototype: true,
// other contraints (if any)...
});
Related pull requests: #5578, #5597
Replace any reference to .noBias
The .noBias
method, previously available on every Arbitrary
, was marked as deprecated in version 3.20.0. It has been replaced by a standalone arbitrary with the same functionality. You can prepare for compatibility with the next major version by updating your code as follows:
--myArbitrary.noBias();
++fc.noBias(myArbitrary);
Related pull requests: #5610
Replace any reference to uuidV
Introduced in version 3.21.0 for uuid
, the version
constraint is intended to replace uuidV
. This change can already be applied in version 3 by making the following update:
--fc.uuidV(4);
++fc.uuid({ version: 4 });
Related pull requests: #5611
Update to v4.x
After applying the recommendations for migrating to the latest v3.x, transitioning to version 4 should be straightforward. However, there are still a few changes to review, either during the upgrade or as you use the updated library. These changes enhance functionality and ensure a more powerful tool by default.
Better type inference
Some typings have been enhanced to ease the user experience:
// In version 3:
fc.constant('a'); // Produces an Arbitrary<string>
fc.constant<'a'>('a'); // Produces an Arbitrary<'a'>
// In version 4:
fc.constant('a'); // Produces an Arbitrary<'a'>
fc.constant<string>('a'); // Produces an Arbitrary<string>
// In version 3:
fc.constantFrom('a', 'b'); // Produces an Arbitrary<string>
fc.constantFrom<'a' | 'b'>('a', 'b'); // Produces an Arbitrary<'a' | 'b'>
// In version 4:
fc.constantFrom('a', 'b'); // Produces an Arbitrary<'a' | 'b'>
fc.constantFrom<string[]>('a', 'b'); // Produces an Arbitrary<string>
Related pull requests: #5577, #5605
Default error reporting
We adopted a new approach to report errors by leveraging "Error Cause", which is already supported by many test runners. Previously, when your predicate threw an Error
, fast-check created a new Error
instance with a message that combined fast-check’s failure details with your original error message.
Now, it attaches your original error as a cause. This approach improves integration with test runners, which often parse error messages for stack trace cleanup and reporting.
If you prefer the previous behavior, you can disable this feature in version 4 by enabling the includeErrorInReport
flag. You can also test this behavior in version 3 by toggling the errorWithCause
flag (renamed to includeErrorInReport
in version 4).
Related pull requests: #5590
Faster scheduler
Since version 1.20.0, fast-check has included a primitive designed to help detect race conditions. This feature unlocked many advanced use cases and elevated the library's capabilities.
However, the previous implementation was slower than intended and allowed intermediate tasks to be created and executed between two scheduled ones. This inconsistency could lead to scenarios where code passed tests but later failed when additional microtasks were introduced. To address this, we have reworked the scheduler in version 4 to be faster, more consistent, and safer.
Consider the following example, where a scheduler instance s
is used:
// `s`: an instance of scheduler provided by fast-check
s.schedule(Promise.resolve(1)).then(async () => {
await 'something already resolved';
s.schedule(Promise.resolve(2));
});
await s.waitAll();
In version 3, all scheduled tasks, including Promise.resolve(2)
, would have been executed by the end of s.waitAll()
. In version 4, however, Promise.resolve(2)
remains pending. This is because during the waitAll
loop, the scheduler processes Promise.resolve(1)
and continues execution until await 'something already resolved'
. At that point, the scheduler resumes its waiting sequence, but Promise.resolve(2)
has not yet been scheduled and remains unknown. As a result, waitAll
finishes before executing it.
This behavior makes the scheduler more predictable and prevents subtle issues. In contrast, version 3 behaved inconsistently when processing many immediately resolved tasks, as shown below:
// `s`: an instance of scheduler provided by fast-check
s.schedule(Promise.resolve(1)).then(async () => {
await 'something already resolved';
await 'something already resolved';
await 'something already resolved';
await 'something already resolved';
await 'something already resolved';
s.schedule(Promise.resolve(2));
});
await s.waitAll();
On this second example version 3 would have behaved as version 4 with Promise.resolve(2)
still pending. The only difference between the two examples being the number of await
before the next scheduled tasks. This improvement ensures unexpected behaviors in such edge cases and ensures consistent behavior.
Related pull requests: #5600, #5604, #5614, #5615
Advanced usages
Custom reporters
The error
field has been removed from the RunDetails
object returned by fc.check
. If you need access to the error message, use the errorInstance
field instead, which was introduced in version 3.0.0.
Related pull requests: #5584
Property execution
If you have implemented a custom class that adheres to the IRawProperty
API required by property runners, or if you have created a custom property runner (e.g., a custom implementation of fc.assert
or fc.check
), this change may affect your code.
The update requires property executors to explicitly call the runBeforeEach
and runAfterEach
hooks. This adjustment can already be made in version 3 by passing true as the second argument to the run method of properties.
Related pull requests: #5581
Refined serializer
In previous major releases, the stringifier algorithm produced outputs like the following:
stringify(Object.create(null)); // 'Object.create(null)'
stringify(Object.assign(Object.create(null), { a: 1 })); // 'Object.assign(Object.create(null),{"a":1})'
Starting with the new major release, the output has been refined to:
stringify(Object.create(null)); // '{__proto__:null}'
stringify(Object.assign(Object.create(null), { a: 1 })); // '{__proto__:null,"a":1}'
This change is unlikely to impact most users. However, we are highlighting it for advanced users who might rely on custom reporting capabilities or stringifier behavior to meet specific needs.
Related pull requests: #5603