# Schema & Property Testing

Two testing tools ship under `effect/testing`:

- **`TestSchema`** — assert how a [`Schema`](https://effect.plants.sh/schema/) decodes, encodes,
  constructs (`make`), and round-trips a value, with one assertion per case.
- **`FastCheck`** — a re-export of the [fast-check](https://fast-check.dev)
  property-based testing library, plus `Schema.toArbitrary` to derive generators
  straight from your schemas.

Both are framework-agnostic. `TestSchema` assertions are plain async functions
(`Promise<void>`), so you call them from a regular `it`/`test` body with `await`
— they are **not** `it.effect` bodies. For property testing wired into
`@effect/vitest` (running an Effect per generated case), see
[`it.effect.prop`](https://effect.plants.sh/testing/writing-tests/).

## Part 1 — Testing Schemas with TestSchema

The entry point is `new TestSchema.Asserts(schema)`. From it you pick a
direction — `decoding()`, `encoding()`, `make()` — and call `succeed` / `fail`.
Every assertion is async, so `await` it.

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"
import { describe, it } from "vitest"

describe("NumberFromString", () => {
  it("decodes and encodes", async () => {
    const asserts = new TestSchema.Asserts(Schema.NumberFromString)

    // decoding: string -> number
    const decoding = asserts.decoding()
    await decoding.succeed("1", 1) // "1" decodes to 1
    await decoding.fail(null, "Expected string, got null")

    // encoding: number -> string
    const encoding = asserts.encoding()
    await encoding.succeed(1, "1") // 1 encodes to "1"
  })
})
```

A few rules that apply to every assertion:

- `succeed(input)` with **one** argument asserts an *identity* — the output
  equals the input unchanged. Use it for schemas that do not transform (e.g.
  `Schema.String`).
- `succeed(input, expected)` with **two** arguments asserts a specific
  transformed output. Use it for codecs like `NumberFromString`.
- `fail(input, message)` compares against the **stringified `Issue`**, not the
  `Issue` object. Pass the exact text the issue produces (e.g.
  `"Expected a value greater than 0, got -1"`). Comparisons use
  `assert.deepStrictEqual`, so structural — not reference — equality is required.
**Where do the error strings come from?:** The `fail` message is exactly what `issue.toString()` returns. The simplest way
to get it right is to run the assertion once, let it fail, and copy the actual
string from the diff.

### `new TestSchema.Asserts(schema)`

Wraps a schema and exposes the per-direction helpers. This is the only
constructor you call directly.

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"

const asserts = new TestSchema.Asserts(Schema.Struct({ name: Schema.String }))
// asserts.decoding() / asserts.encoding() / asserts.make() / asserts.arbitrary()
// asserts.verifyLosslessTransformation()
```

### `asserts.decoding(options?)`

Returns a `Decoding` instance for testing the decode direction (unknown input →
the schema's `Type`). Pass `parseOptions` to control error reporting, e.g.
`{ errors: "all" }` to collect every issue instead of stopping at the first.

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"

const schema = Schema.FiniteFromString.check(Schema.isGreaterThan(0))
const decoding = new TestSchema.Asserts(schema).decoding()

await decoding.succeed("1", 1) // "1" -> 1
await decoding.fail("-1", "Expected a value greater than 0, got -1")
await decoding.fail("a", "Expected a finite number, got NaN")

// collect all issues for a struct instead of failing fast
const all = new TestSchema.Asserts(
  Schema.Struct({ a: Schema.String, b: Schema.Number })
).decoding({ parseOptions: { errors: "all" } })
```

#### `decoding.succeed(input)` / `decoding.succeed(input, expected)`

One argument asserts identity; two arguments assert a transformed result.

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"

// identity: String does not transform
await new TestSchema.Asserts(Schema.String).decoding().succeed("hello")
// => ok, output === "hello"

// transformed
await new TestSchema.Asserts(Schema.NumberFromString).decoding().succeed("42", 42)
// => ok, "42" decoded to 42
```

#### `decoding.fail(input, message)`

Asserts decoding fails with the given stringified issue.

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"

await new TestSchema.Asserts(Schema.String).decoding().fail(42, "Expected string, got 42")
// => ok, decoding 42 produced that issue
```

#### `decoding.provide(key, implementation)`

Returns a new `Decoding` with a service injected into the decoder's context. Use
it when a schema's decoder depends on a [service](https://effect.plants.sh/services-and-layers/) (for
example a schema built with `Schema.decode` whose getter reads a service).

```ts
import { Context, Effect, Option, Schema, SchemaGetter, SchemaIssue } from "effect"
import { TestSchema } from "effect/testing"

class Service extends Context.Service<Service, { fallback: Effect.Effect<string> }>()("Service") {}

const schema = Schema.String.pipe(
  Schema.decode({
    decode: SchemaGetter.checkEffect((s) =>
      Effect.gen(function*() {
        yield* Service
        if (s.length === 0) {
          return new SchemaIssue.InvalidValue(Option.some(s), {
            message: "input should not be empty string"
          })
        }
      })
    ),
    encode: SchemaGetter.passthrough()
  })
)

const decoding = new TestSchema.Asserts(schema)
  .decoding()
  .provide(Service, { fallback: Effect.succeed("b") })

await decoding.succeed("a")
await decoding.fail("", "input should not be empty string")
```

### `asserts.encoding(options?)`

The mirror of `decoding()`: returns an `Encoding` instance that exercises the
encode direction (the schema's `Type` → its `Encoded` form). Same `succeed` /
`fail` / `provide` API and the same `parseOptions`.

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"

const schema = Schema.FiniteFromString.check(Schema.isGreaterThan(0))
const encoding = new TestSchema.Asserts(schema).encoding()

await encoding.succeed(1, "1") // 1 -> "1"
await encoding.fail(-1, "Expected a value greater than 0, got -1")
```

Pairing both directions verifies a codec round-trips for a concrete value:

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"

const asserts = new TestSchema.Asserts(Schema.NumberFromString)
await asserts.decoding().succeed("3.14", 3.14) // string -> number
await asserts.encoding().succeed(3.14, "3.14") // number -> string
```

#### `encoding.succeed` / `encoding.fail` / `encoding.provide`

Behave exactly like their `Decoding` counterparts, but in the encode direction.
`succeed(input)` asserts identity, `succeed(input, expected)` asserts the encoded
output, `fail(input, message)` asserts an encoding failure, and `provide` injects
a service the encoder requires.

```ts
import { Context, Effect, Schema, SchemaGetter } from "effect"
import { TestSchema } from "effect/testing"

class Service extends Context.Service<Service, { fallback: Effect.Effect<string> }>()("Service") {}

const schema = Schema.String.pipe(
  Schema.decode({
    decode: SchemaGetter.passthrough(),
    encode: SchemaGetter.checkEffect(() => Effect.as(Service, undefined))
  })
)

const encoding = new TestSchema.Asserts(schema)
  .encoding()
  .provide(Service, { fallback: Effect.succeed("b") })

await encoding.succeed("a")
```

### `asserts.make(options?)`

Returns `{ succeed, fail }` for the schema's `make` (construction) operation —
how a schema accepts, transforms, or rejects in-memory construction input.
`succeed(input)` asserts `make` returns the input unchanged, `succeed(input,
expected)` asserts the constructed value, and `fail(input, message)` asserts
construction fails.

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"

const make = new TestSchema.Asserts(Schema.String).make()
await make.succeed("hello") // => constructs "hello"

// a constrained schema rejects out-of-range construction input
const positive = new TestSchema.Asserts(
  Schema.Number.check(Schema.isGreaterThan(0))
).make()
await positive.fail(-1, "Expected a value greater than 0, got -1")
```

### `asserts.verifyLosslessTransformation(options?)`

A property-based assertion: FastCheck generates arbitrary values of the schema's
`Type`, encodes each, decodes it back, and asserts the result equals the
original. This proves a codec is lossless across many inputs in one line. Pass
`options.params` to tune FastCheck (for example `numRuns`).

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"
import { it } from "vitest"

it("round-trips", async () => {
  const asserts = new TestSchema.Asserts(
    Schema.FiniteFromString.check(Schema.isGreaterThan(0))
  )
  // encode -> decode === original, for every generated value
  await asserts.verifyLosslessTransformation()

  // with more runs
  await asserts.verifyLosslessTransformation({ params: { numRuns: 1000 } })
})
```

### `asserts.arbitrary().verifyGeneration(options?)`

Generates arbitrary values for the schema and asserts each one satisfies the
schema's `is` predicate — i.e. the derived generator only produces valid values.
Defaults to 20 runs; override via `options.params`.

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"

new TestSchema.Asserts(Schema.String).arbitrary().verifyGeneration()
// => generates 20 strings, asserts Schema.is(Schema.String) for each

new TestSchema.Asserts(
  Schema.Struct({ name: Schema.String, age: Schema.Number })
)
  .arbitrary()
  .verifyGeneration({ params: { numRuns: 100 } })
```

### `TestSchema.Asserts.ast.fields.equals(a, b)`

A static helper. Asserts that two sets of struct fields produce the same AST
(via `assert.deepStrictEqual` over `SchemaAST.getAST` of each field). Useful for
testing schema-building helpers that should yield equivalent field shapes.

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"

TestSchema.Asserts.ast.fields.equals(
  { name: Schema.String },
  { name: Schema.String }
)
// => no error (structurally equal)
```

### `TestSchema.Asserts.ast.elements.equals(a, b)`

The tuple-element counterpart: asserts two tuple element lists produce the same
element ASTs.

```ts
import { Schema } from "effect"
import { TestSchema } from "effect/testing"

TestSchema.Asserts.ast.elements.equals(
  [Schema.String, Schema.Number],
  [Schema.String, Schema.Number]
)
// => no error
```

## Part 2 — Property-based testing with FastCheck

`` re-exports the whole
[fast-check](https://fast-check.dev) library. Property-based testing flips the
usual model: instead of hand-picking inputs, you state a *property* that should
hold for **all** inputs, FastCheck generates many random values, and on failure
it *shrinks* the counterexample down to the smallest input that breaks the
property.

```ts
import { FastCheck } from "effect/testing"
import { it } from "vitest"

it("reverse of reverse is identity", () => {
  const property = FastCheck.property(
    FastCheck.array(FastCheck.integer()),
    (xs) => {
      const twice = xs.slice().reverse().reverse()
      return JSON.stringify(twice) === JSON.stringify(xs)
    }
  )
  // generate inputs, run the predicate, throw on a counterexample
  FastCheck.assert(property)
})
```

`FastCheck.property` takes one or more arbitraries plus a predicate that receives
one generated value per arbitrary and returns a boolean (or asserts internally
and returns `void`). `FastCheck.assert` runs it.

### `FastCheck.asyncProperty`

Like `property`, but the predicate returns a `Promise`. Run it with the same
`FastCheck.assert` (which awaits async properties).

```ts
import { FastCheck } from "effect/testing"
import { it } from "vitest"

it("async predicate", async () => {
  const property = FastCheck.asyncProperty(
    FastCheck.string(),
    async (s) => {
      const echoed = await Promise.resolve(s)
      return echoed === s
    }
  )
  await FastCheck.assert(property)
})
```

### Commonly used arbitraries and utilities

Each entry below is a fast-check export reachable as `FastCheck.*`. These are
the building blocks you compose into properties.

#### `FastCheck.integer` / `FastCheck.string` / `FastCheck.boolean`

Primitive generators. `integer` and `string` accept constraints such as
`{ min, max }` and `{ minLength, maxLength }`.

```ts
import { FastCheck } from "effect/testing"

FastCheck.sample(FastCheck.integer({ min: 0, max: 10 }), 3)
// => e.g. [7, 0, 4]
FastCheck.sample(FastCheck.string({ minLength: 1 }), 2)
// => e.g. ["a9", "Q"]
```

#### `FastCheck.array`

Generates arrays of a given element arbitrary; accepts `{ minLength, maxLength }`.

```ts
import { FastCheck } from "effect/testing"

FastCheck.sample(FastCheck.array(FastCheck.integer(), { maxLength: 3 }), 2)
// => e.g. [[1], [4, -2, 0]]
```

#### `FastCheck.record`

Generates an object from a record of arbitraries — one per key.

```ts
import { FastCheck } from "effect/testing"

const person = FastCheck.record({
  name: FastCheck.string({ minLength: 1 }),
  age: FastCheck.integer({ min: 0, max: 120 }),
  email: FastCheck.emailAddress()
})
FastCheck.sample(person, 1)
// => e.g. [{ name: "Ada", age: 36, email: "a@b.cd" }]
```

#### `FastCheck.constant`

Always generates the same value — handy as a branch in `oneof`.

```ts
import { FastCheck } from "effect/testing"

FastCheck.sample(FastCheck.constant("fixed"), 2)
// => ["fixed", "fixed"]
```

#### `FastCheck.oneof`

Picks from several arbitraries, producing a union of their outputs.

```ts
import { FastCheck } from "effect/testing"

const status = FastCheck.oneof(
  FastCheck.constant("active"),
  FastCheck.constant("inactive")
)
FastCheck.sample(status, 3)
// => e.g. ["active", "inactive", "active"]
```

#### `FastCheck.emailAddress`

Generates syntactically valid email strings.

```ts
import { FastCheck } from "effect/testing"

FastCheck.sample(FastCheck.emailAddress(), 1)
// => e.g. ["foo.bar@example.com"]
```

#### `FastCheck.sample`

Draws a fixed number of values from an arbitrary without running a property —
useful for exploring what a generator produces.

```ts
import { FastCheck } from "effect/testing"

FastCheck.sample(FastCheck.boolean(), 4)
// => e.g. [true, false, false, true]
```

#### `FastCheck.assert`

Runs a property (sync or async) and throws on the first counterexample, reporting
the shrunk input. Accepts a second `Parameters` argument to tune the run.

```ts
import { FastCheck } from "effect/testing"

FastCheck.assert(
  FastCheck.property(FastCheck.integer(), (n) => Number.isInteger(n)),
  { numRuns: 500 } // run 500 cases instead of the default
)
```

#### `FastCheck.Parameters` and `numRuns`

`FastCheck.Parameters<Ts>` is the options type accepted by `assert`. The most
common field is `numRuns` (how many cases to generate); others include `seed` and
`endOnFailure` for reproducible runs.

```ts
import { FastCheck } from "effect/testing"

const params: FastCheck.Parameters<[number]> = { numRuns: 100, seed: 42 }
FastCheck.assert(
  FastCheck.property(FastCheck.integer(), (n) => n - n === 0),
  params
)
```

#### `FastCheck.Arbitrary`

The type of a generator. `Schema.toArbitrary` returns a
`FastCheck.Arbitrary<S["Type"]>`, and every `FastCheck.*` builder above produces
one.

```ts
import { FastCheck } from "effect/testing"

const ageArb: FastCheck.Arbitrary<number> = FastCheck.integer({ min: 0, max: 120 })
```

### Deriving arbitraries from a Schema

You rarely have to hand-build arbitraries for your domain types: any
[`Schema`](https://effect.plants.sh/schema/) can produce one with `Schema.toArbitrary`, returning a
`FastCheck.Arbitrary` of the schema's `Type`. This keeps generators in sync with
validation — see [Schema derivations](https://effect.plants.sh/schema/derivations/).

```ts
import { Schema } from "effect"
import { FastCheck } from "effect/testing"
import { it } from "vitest"

const Person = Schema.Struct({
  name: Schema.String,
  age: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))
})

const personArb = Schema.toArbitrary(Person) // FastCheck.Arbitrary<{ name: string; age: number }>

it("every generated person is non-negative age", () => {
  FastCheck.assert(
    FastCheck.property(personArb, (p) => p.age >= 0)
  )
})
```
**Use it.effect.prop for Effect-based properties:** When the property under test is an Effect (it needs services, the clock, or
randomness), reach for [`it.effect.prop`](https://effect.plants.sh/testing/writing-tests/) from
`@effect/vitest`. It accepts `Schema` arbitraries directly and runs an Effect per
generated case, shrinking on failure — no manual `Schema.toArbitrary` +
`FastCheck.assert` plumbing.