GitHub

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝘇𝗼𝗱-𝘁𝗲𝘀𝘁

Testing utility that generates arbitrary, pseudorandom zod schemas, powered by fast-check

NPM Version   TypeScript   License   npm  

Static Badge   Static Badge   Static Badge  

Requirements

@traversable/zod-test has 2 peer dependencies:

  1. zod (v4)
  2. fast-check

Usage

$ pnpm add -D @traversable/zod-test zod fast-check

Here's an example of importing the library:

import { z } from 'zod'
import { zxTest } from '@traversable/zod-test'
// see below for specifc examples

Track record

@traversabe/zod-test has found several upstream bugs in zod:

  1. Security exploit: z.object pollutes the global Object prototype
  1. Bug: z.literal escaping bug
  1. Bug: "Diagonal" objects passed to z.enum produce false negatives
  1. Bug: z.file output type incompatible with globalThis.File

Table of contents

zxTest.fuzz

Convert a Zod schema into a fast-check arbitrary.

Configure how fuzzed values will be generated via the 2nd argument (options).

Override individual arbitraries via the 3rd argument (overrides).

Note

zxTest.fuzz is the only schema-to-generator function that has itself been fuzz tested to ensure that no matter what schema you give it, the data-generator it returns will always produce valid data.

This excludes schemas that make it impossible to generate valid data, for example:

  • z.never
  • z.nonoptional(z.undefined())
  • z.enum([])
  • z.union([])
  • z.intersection(z.number(), z.string())

Example

import * as vi from 'vitest'
import * as fc from 'fast-check'
import { fuzz } from '@traversable/zod-test'
const Schema = z.record(
  z.string(),
  z.union(
    z.number(),
    z.string(),
  )
)
const generator = fuzz(
  Schema,
  { record: { minKeys: 1 }, number: { noDefaultInfinity: true } },
  { string: () => fc.stringMatching(/[\S\s]+[\S]+/) },
)
vi.test('fuzz test example', () => {
  fc.assert(
    fc.property(generator, (data) => {
      vi.assert.doesNotThrow(() => Schema.parse(data))
    }),
    { numRuns: 1_000 }
  )
})

See also

zxTest.seedToSchema

Use zxTest.seedToSchema to convert a seed generated by zxTest.SeedGenerator into a zod schema that satisfies the configuration options you specified.

Example

import { zxTest } from '@traversable/zod-test'
import * as fc from 'fast-check'
const builder = zxTest.SeedGenerator()['*']
const [mySeed] = fc.sample(builder.object, 1)
const mySchema = zxTest.seedToSchema(mySeed)
//    ^? const mySchema: z.ZodType

zxTest.seedToValidData

Use zxTest.seedToValidData to convert a seed generated by zxTest.SeedGenerator into data that satisfies the schema that the seed represents.

Example

import { zxTest } from '@traversable/zod-test'
import * as fc from 'fast-check'
const builder = zxTest.SeedGenerator()['*']
const [mySeed] = fc.sample(builder.object, 1)
const mySchema = zxTest.seedToSchema(mySeed)
//    ^? const mySchema: z.ZodType
const validData = zxTest.seedToValidData(mySeed)
mySchema.parse(validData) // will never throw

zxTest.seedToInvalidData

Use zxTest.seedToInvalidData to convert a seed generated by zxTest.SeedGenerator into data that does not satisfy the schema that the seed represents.

Example

import { zxTest } from '@traversable/zod-test'
import * as fc from 'fast-check'
const builder = zxTest.SeedGenerator()['*']
const [mySeed] = fc.sample(builder.object, 1)
const mySchema = zxTest.seedToSchema(mySeed)
//    ^? const mySchema: z.ZodType
const invalidData = zxTest.seedToValidData(mySeed)
mySchema.parse(invalidData) // should always throw

zxTest.seedToValidDataGenerator

Like zxTest.seedToValidData, except zxTest.seedToValidDataGenerator accepts a seed and returns a valid data arbitrary (which can then be used to produce valid data).

Example

import { zxTest } from '@traversable/zod-test'
import * as fc from 'fast-check'
const builder = zxTest.SeedGenerator()['*']
const [mySeed] = fc.sample(builder.object, 1)
const mySchema = zxTest.seedToSchema(mySeed)
//    ^? const mySchema: z.ZodType
const validDataGenerator = zxTest.seedToValidDataGenerator(mySeed)
const [validData] = fc.sample(validDataGenerator, 1)
mySchema.parse(validData) // will never throw

zxTest.seedToInvalidDataGenerator

Like zxTest.seedToInvalidData, except zxTest.seedToValidDataGenerator accepts a seed and returns an invalid data arbitrary (which can then be used to produce invalid data).

Example

import type * as z from 'zod'
import * as fc from 'fast-check'
import { zxTest } from '@traversable/zod-test'
const builder = zxTest.SeedGenerator()['*']
const [mySeed] = fc.sample(builder.object, 1)
const mySchema = zxTest.seedToSchema(mySeed)
//    ^? const mySchema: z.ZodType
const invalidDataGenerator = zxTest.seedToInvalidDataGenerator(mySeed)
const [invalidData] = fc.sample(invalidDataGenerator, 1)
mySchema.parse(invalidData) // will always throw

zxTest.SeedGenerator

Note

zxTest.SeedGenerator is fairly low-level. All of the other exports of this library have been implemented in terms of zxTest.SeedGenerator.

Generates a configurable, pseudo-random "seed builder".

Example

import { zxTest } from '@traversable/zod-test'
import * as fc from 'fast-check'
const builder = zxTest.SeedGenerator({
  include: ["boolean", "string", "object"],
  // 𐙘 use `include` to only include certain schema types
  exclude: ["boolean", "any"],
  // 𐙘 use `exclude` to exclude certain schema types altogether (overrides `include`)
  object: { maxKeys: 5 },
  // 𐙘 specific arbitraries are configurable by name
})
// included schemas are present as properties on your generator...
builder.string
builder.object
// ...excluded schemas are not present...
builder.boolean // 🚫 TypeError
// ...a special wildcard `"*"` property (pronounced "surprise me") is always present:
builder["*"]
/**
 * `fast-check` will generate a seed, which is a data structure containing
 * integers that represent a kind of AST.
 *
 * To use a seed, you need to pass it to an interpreter like `zxTest.seedToSchema`,
 * `zxTest.seedToValidData` or `zxTest.seedToInvalidData`:
 */
const [mySeed] = fc.sample(builder.object, 1)
const mySchema = zxTest.seedToSchema(mySeed)
//    ^? const mySchema: z.ZodType
const validData = zxTest.seedToValidData(mySeed)
//    ^? since the `mySeed` was also used to generate `mySchema`,
//       parsing `validData` should always succeed
const invalidData = zxTest.seedToInvalidData(mySeed)
//    ^? since the `mySeed` was also used to generate `mySchema`,
//       parsing `invalidData` should always fail

zxTest.SeedValidDataGenerator

Like zxTest.SeedGenerator, except zxTest.SeedValidDataGenerator comes pre-configured to exclude schemas that make it impossible to reliably generate valid data.

Note

zxTest.SeedValidDataGenerator does not accept any options. If you need more fine-grained control of the schemas being generated, use zxTest.SeedGenerator.

zxTest.SeedInvalidDataGenerator

Like zxTest.SeedGenerator, except zxTest.SeedValidDataGenerator comes pre-configured to exclude schemas that make it impossible to reliably generate invalid data.

Note

zxTest.SeedInvalidDataGenerator does not accept any options. If you need more fine-grained control of the schemas being generated, use zxTest.SeedGenerator.

Read the original on github.com ↗