Generate fast-check arbitraries from Valibot schemas for property-based testing.
pnpm add valibot-fast-check
# or
npm install valibot-fast-checkimport { vfc } from "valibot-fast-check";
import * as v from "valibot";
import fc from "fast-check";
// Define a Valibot schema
const UserSchema = v.object({
name: v.pipe(v.string(), v.minLength(1), v.maxLength(50)),
age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(120)),
email: v.pipe(v.string(), v.email()),
});
// Generate fast-check arbitraries
const userArbitrary = vfc().inputOf(UserSchema);
// Use in property-based tests
fc.assert(
fc.property(userArbitrary, (user) => {
// Your test logic here
const result = v.safeParse(UserSchema, user);
expect(result.success).toBe(true);
}),
);Generates arbitraries that produce valid input values for the given schema.
const schema = v.pipe(v.string(), v.email());
const arbitrary = vfc().inputOf(schema);
// Generates valid email stringsGenerates arbitraries that produce parsed/transformed output values for schemas with transformations.
const schema = v.pipe(v.string(), v.trim(), v.minLength(1));
const arbitrary = vfc().outputOf(schema);
// Generates trimmed, non-empty stringsOverride generation for specific schemas with custom arbitraries.
const customSchema = v.string();
const customArbitrary = fc.constant("fixed-value");
const generator = vfc().override(customSchema, customArbitrary);
// Will use customArbitrary when encountering customSchema- ✅
string- length (minLength,maxLength,length), content (startsWith,endsWith,includes,trim), formats (email,uuid,url), exact values (value,values) - ✅
number- range (minValue,maxValue,gtValue,ltValue), constraints (integer,finite,safeInteger,multipleOf), exact values (value,values) - ✅
bigint- range constraints and exact values - ✅
boolean- with value constraints - ✅
date- with range constraints and exact values - ✅
undefined,null,void,any,unknown - ✅
nan- generatesNumber.NaN
- ✅
object- recursive object generation - ✅
array- with length constraints - ✅
tuple- fixed-length arrays with typed elements - ✅
map- generatesMapinstances - ✅
set- generatesSetinstances with size constraints
- ✅
optional- usesfc.option()with undefined - ✅
nullable- usesfc.option()with null - ✅
nullish- generates value, null, or undefined - ✅
enum/picklist- picks from enum values - ✅
literal- generates exact literal values - ✅
union- generates from union alternatives - ✅
function- generates callable functions - ✅
symbol- generates unique symbols
This library includes several performance optimizations over naive filtering approaches:
Instead of generating random numbers and filtering:
// ❌ Slow: generates any number, filters most out
fc.integer().filter((x) => x >= 10 && x <= 20);
// ✅ Fast: generates only valid range
fc.integer({ min: 10, max: 20 });Direct generation for exact values:
// Schema: v.pipe(v.number(), v.value(42))
// ✅ Generates: fc.constant(42)
// Schema: v.pipe(v.string(), v.values(["a", "b", "c"]))
// ✅ Generates: fc.constantFrom("a", "b", "c")Built-in optimizations for string constraints:
// Format constraints
// Schema: v.pipe(v.string(), v.email())
// ✅ Generates: fc.emailAddress() with Valibot's email regex
// Schema: v.pipe(v.string(), v.uuid())
// ✅ Generates: fc.uuid()
// Schema: v.pipe(v.string(), v.url())
// ✅ Generates: fc.webUrl()
// Content constraints (when used individually)
// Schema: v.pipe(v.string(), v.startsWith("prefix"))
// ✅ Generates: fc.string().map(s => "prefix" + s)
// Schema: v.pipe(v.string(), v.endsWith("suffix"))
// ✅ Generates: fc.string().map(s => s + "suffix")
// Multiple content constraints fall back to filterBySchemaFor complex or custom constraints, falls back to schema validation with efficiency monitoring:
// Custom validations automatically use filterBySchema
const schema = v.pipe(
v.number(),
v.check((x) => isPrime(x)), // Custom validation
);
// Constraints that can't be optimized
const schema2 = v.pipe(
v.number(),
v.notValue(5), // Uses filterBySchema
v.notValues([1, 2, 3]), // Uses filterBySchema
);
// Multiple string content constraints
const schema3 = v.pipe(
v.string(),
v.startsWith("hello"),
v.endsWith("world"),
v.includes("test"), // Falls back to filterBySchema
);The library provides detailed error messages for unsupported schemas and generation failures:
// Unsupported schema type
try {
vfc().inputOf(unsupportedSchema);
} catch (error) {
// VFCUnsupportedSchemaError: Unable to generate valid values for Valibot schema. CustomType schemas are not supported.
}
// Low success rate from filterBySchema
try {
const restrictiveSchema = v.pipe(
v.number(),
v.check((x) => x === Math.PI), // Extremely low success rate
);
const samples = fc.sample(vfc().inputOf(restrictiveSchema), 10);
} catch (error) {
// VFCGenerationError: Unable to generate valid values for the passed Valibot schema.
// Please provide an override for the schema at path '.'.
}import { vfc } from "valibot-fast-check";
import * as v from "valibot";
import fc from "fast-check";
const schema = v.object({
username: v.pipe(v.string(), v.minLength(3), v.maxLength(20)),
password: v.pipe(v.string(), v.minLength(8)),
age: v.pipe(v.number(), v.integer(), v.minValue(13)),
});
fc.assert(
fc.property(vfc().inputOf(schema), (data) => {
// Test that all generated data is valid
const result = v.safeParse(schema, data);
expect(result.success).toBe(true);
// Test business logic
expect(data.username.length).toBeGreaterThanOrEqual(3);
expect(data.age).toBeGreaterThanOrEqual(13);
}),
);const AddressSchema = v.object({
street: v.string(),
city: v.string(),
zipCode: v.pipe(v.string(), v.regex(/^\d{5}$/)),
});
const PersonSchema = v.object({
name: v.string(),
addresses: v.array(AddressSchema),
primaryAddress: v.optional(AddressSchema),
});
const personArbitrary = vfc().inputOf(PersonSchema);
// Generates complex nested objects with arrays and optional fieldsconst schema = v.object({
id: v.string(), // We want specific ID format
data: v.any(),
});
const customGenerator = vfc().override(
v.string(),
fc.uuid(), // All strings will be UUIDs
);
const arbitrary = customGenerator.inputOf(schema);- String combinations: Multiple content constraints with length requirements fall back to
filterBySchema - Custom validations:
v.check()andv.custom()always use filtering and may have low success rates - Unsupported constraints:
v.notValue(),v.notValues()use filtering (low efficiency for large exclusion sets) - Date generation: May occasionally produce
NaNdates due to fast-check limitations - Regex constraints:
v.regex()not yet optimized (usesfilterBySchema)
Inspired by zod-fast-check. This library brings the same concept to Valibot, leveraging Valibot's smaller bundle size.