Generate fast-check arbitraries from Valibot schemas for property-based testing.
pnpm add valibot-fast-check
# or
npm install valibot-fast-check
import { 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 strings
Generates 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 strings
Override 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
- generatesMap
instances - ✅
set
- generatesSet
instances 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 filterBySchema
For 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 fields
const 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
NaN
dates 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.