Plugins
Plugins provide a way to extend and refine the runtime and execution behavior of your properties. In the same way building custom arbitraries gives you the flexibility to tweak generation flows, plugins give you ways to customize how your properties run.
Plugins are designed to support things such as:
- Executing something before the predicate
- Stopping a predicate running for too long
- Capturing key insights about the execution flows including timings for observability
Using plugins
Plugins can be passed as part of the customizations accepted by the fc.assert runner.
await fc.assert(fc.asyncProperty(...arbs, predicate), {
plugins: [pluginA(...paramsForPluginA), pluginB(...paramsForPluginB)],
});
Installing plugins globally
Plugins needed by all your properties can be installed once, typically in a setup file, with installGlobalPlugin.
fc.installGlobalPlugin(pluginA());
Installed plugins run before the ones passed to the runner, so the snippet above followed by fc.assert(myProp, { plugins: [pluginB()] }) is equivalent to plugins: [pluginA(), pluginB()].
Combining plugins
Plugins have been designed to be combined together. When multiple plugins get defined for the same assertion, they execute in declaration order. The first plugin runs first and wraps the execution of the second one until we reach the main operation. Plugins can wrap various stages of the flow, including the execution of the predicate function.
Let's illustrate how plugins get combined on a property checking a search endpoint. In this example, the database is brought back to a clean state before each run:
await fc.assert(
fc.asyncProperty(fc.string(), async (query) => {
const results = await searchService.search(query);
// ...assertions on the results...
}),
{
plugins: [
timeout(10_000),
beforeEach(async () => {
await database.reset(); // bring back a clean state, possibly slow
}),
timeout(5_000),
],
},
);
The plugin setup above makes use of two instances of the timeout plugin. The first instance is responsible for making sure that the time taken by the beforeEach cleanup plus the one taken by the predicate never exceeds 10 seconds. The second one only wraps the predicate and makes sure that it won't take more than 5 seconds.
This short example clearly highlights why the declaration order of plugins is crucial. Two different orders may result in totally different expectations. Correctly ordering them will make you capable of finely configuring your flows to fit your needs.
The plugins
We come up with a set of plugins to extend the library using built-in plugins. The following pages provide extended details and deep dive into each of them.
Life-cycle
Life-cycle plugins provide hooks to prepare or clean up things for your predicates. They wrap every execution of the predicate including the ones linked to shrinking.
Time limits
Time-limit plugins bound the time spent assessing your properties. They can come into play at various levels, from an isolated execution of a predicate to the full run of the assertion.