# Testing

Effect programs are values that describe work, so testing them is mostly a
matter of running those values and asserting on the result. The `@effect/vitest`
package wires Effect into [Vitest](https://vitest.dev) so a test can simply
*return an Effect*, and the surrounding machinery runs it, fails the test on an
unexpected error, and tears down any scoped resources for you.

Because Effect models time, services, and randomness as data rather than calling
into the platform directly, tests can be **fully deterministic**. Sleeps,
timeouts, retries, and schedules are driven by a controllable `TestClock`
instead of real wall-clock time, and the services your code depends on are
swapped for in-memory test implementations through `Layer`.

```ts
import { assert, describe, it } from "@effect/vitest"
import { Effect } from "effect"

// A test is just an Effect. `it.effect` runs it and provides the test
// services (TestClock, TestConsole) automatically.
describe("greeting", () => {
  it.effect("uppercases the name", () =>
    Effect.gen(function*() {
      const result = "ada".toUpperCase()
      assert.strictEqual(result, "ADA")
    }))
})
```

## Why tests are deterministic

An Effect never *does* anything by itself — it is a description that the Effect
runtime interprets. That description reaches for time, the console, randomness,
and your application services through the [context](https://effect.plants.sh/services-and-layers/), so a
test can replace any of them. `it.effect` does exactly this: it runs your Effect
against a runtime where two services are swapped for test-friendly versions.

- **`TestClock`** — time does not advance on its own. The clock starts at the
  epoch (`0`) and only moves when *you* call `TestClock.adjust`. A program that
  sleeps for an hour completes instantly once you advance the clock by an hour,
  with no real waiting. See [TestClock](https://effect.plants.sh/testing/testclock/).
- **`TestConsole`** — `Console.log` / `Console.error` calls are captured in
  memory instead of being printed, so you can assert on what a program logged.

Everything else (the order of fibers, retries, schedules) is driven by these
deterministic services, so the same test produces the same result every run.

## Setup

`@effect/vitest` builds on Vitest. Add both as dev dependencies:

```sh
pnpm add -D vitest @effect/vitest
```

Then ```

This `it` is the standard Vitest test function extended with Effect-aware
methods. The most important ones:

| Method           | Description                                                                  |
| ---------------- | ---------------------------------------------------------------------------- |
| `it.effect`      | Run an Effect with the test services provided (`TestClock`, `TestConsole`).  |
| `it.live`        | Run an Effect against the live runtime — real clock, real console.           |
| `it.effect.each` | Run the same Effect test over a table of cases.                              |
| `it.effect.prop` | Property-based testing: generate inputs from `Schema` arbitraries.           |
| `it.flakyTest`   | Retry an Effect until it succeeds (or a timeout elapses).                    |
| `layer(...)`     | Build a layer once and share its services across every test in a block.      |

Assertions come from the same import: the `assert` namespace re-exports
Vitest's `assert` (`assert.strictEqual`, `assert.deepStrictEqual`,
`assert.isTrue`, ...). Effect-aware helpers for `Option`, `Result`, and `Exit`
(`assertSome`, `assertSuccess`, `assertExitSuccess`, ...) live in
`@effect/vitest/utils` (see the [assertions reference](#assertions-reference-effectvitestutils)
below).

import { Aside } from "@astrojs/starlight/components"
**Prefer assert over expect:** Inside `it.effect` tests, prefer the `assert.*` helpers (and the
  `@effect/vitest/utils` helpers) over Vitest's `expect`. They throw synchronous
  assertion errors that integrate cleanly with the Effect that the test returns,
  and the Effect-aware helpers understand `Option`, `Result`, and `Exit`.

## The grab bag

The `@effect/vitest` patterns you reach for most. Each has a fuller treatment on
the sub-pages linked at the bottom; this is the shortlist.

### Run an Effect as a test, capture failures

```ts
// `it.effect` provides TestClock + TestConsole and a Scope, runs the Effect,
// and fails the test if it fails unexpectedly.
it.effect("adds", () =>
  Effect.gen(function*() {
    assert.strictEqual(yield* Effect.succeed(1 + 1), 2)
  }))

// A failing Effect would abort the test — capture the outcome with Effect.exit
// (or Effect.result) and assert on the value instead.
it.effect("fails as expected", () =>
  Effect.gen(function*() {
    const exit = yield* Effect.exit(Effect.fail("DivByZero"))
    assert.deepStrictEqual(exit, Exit.fail("DivByZero"))
  }))
```

### Run against the live runtime with `it.live`

```ts
// Real Clock, real Console, real randomness — use it only when you must observe
// actual timing. A program that sleeps will actually wait.
it.live("reads real time", () =>
  Effect.gen(function*() {
    const millis = yield* Clock.currentTimeMillis
    assert.isTrue(millis > 0) // => real epoch millis, not 0
  }))
```

### Drive virtual time with `TestClock`

```ts
// Fork the time-dependent work, advance the clock, then join and assert.
it.effect("completes a one-hour sleep instantly", () =>
  Effect.gen(function*() {
    const fiber = yield* Effect.forkChild(
      Effect.sleep("1 hour").pipe(Effect.as("done" as const))
    )
    yield* TestClock.adjust("1 hour")
    assert.strictEqual(yield* Fiber.join(fiber), "done")
  }))
```

### Table-driven tests with `each`

```ts
// One test per row; `%#` in the name is replaced with the case index.
it.effect.each([
  { input: "ada", expected: "ADA" },
  { input: "grace", expected: "GRACE" }
])("uppercases %#", ({ input, expected }) =>
  Effect.gen(function*() {
    assert.strictEqual(input.toUpperCase(), expected)
  }))
```

### Property-based tests from `Schema`

```ts
// Inputs are generated from Schema arbitraries (array form -> positional,
// record form -> named) and checked over many random samples.
it.effect.prop("addition commutes", [Schema.Number, Schema.Number], ([a, b]) =>
  Effect.gen(function*() {
    assert.strictEqual(a + b, b + a)
  }))
```

### Provide a test layer for a service

```ts
class Greeter extends Context.Service<Greeter, {
  readonly hello: (name: string) => string
}>()("Greeter") {
  static readonly layer = Layer.succeed(Greeter, {
    hello: (name) => `Hello, ${name}!`
  })
}

// `layer(...)` builds the layer once and shares it across the block; the `it`
// passed to the callback already has the layer's services in context.
layer(Greeter.layer)("Greeter", (it) => {
  it.effect("greets", () =>
    Effect.gen(function*() {
      const greeter = yield* Greeter
      assert.strictEqual(greeter.hello("Ada"), "Hello, Ada!")
    }))
})
```

### Retry a genuinely flaky Effect

```ts
// `flakyTest` is a plain combinator (not an it.* definition): it retries the
// Effect until it succeeds or the timeout elapses. Reach for it sparingly.
it.effect("eventually succeeds", () =>
  it.flakyTest(
    Effect.sync(() => {
      if (Math.random() > 0.1) throw new Error("retry me")
    }),
    "5 seconds" // => Duration.Input
  ))
```

## Capturing console output with TestConsole

Because `it.effect` provides `TestConsole`, any `Console.log` / `Console.error`
performed by your program is recorded instead of printed. Read it back with
`TestConsole.logLines` and `TestConsole.errorLines` from `effect/testing`:

```ts
describe("audit", () => {
  it.effect("logs each step", () =>
    Effect.gen(function*() {
      yield* Console.log("starting")
      yield* Console.error("oops")

      const logs = yield* TestConsole.logLines
      const errors = yield* TestConsole.errorLines

      assert.deepStrictEqual(logs, ["starting"]) // => captured Console.log args
      assert.deepStrictEqual(errors, ["oops"]) // => captured Console.error args
    }))
})
```

`logLines` and `errorLines` return the original arguments passed to the console
calls, flattened in call order — not formatted strings. Only code that goes
through Effect's `Console` service is captured; direct `globalThis.console`
calls bypass the test console. `TestConsole` has no dedicated page; everything
it exposes (`layer`, `make`, `testConsoleWith`, `logLines`, `errorLines`) is
covered here.

## Other @effect/vitest exports

Beyond the `it.*` methods above (covered on the sub-pages), `@effect/vitest`
exports a few helpers for wiring and customizing your test setup. The package
also re-exports all of `vitest` (`describe`, `expect`, `vi`, `beforeEach`, ...),
so you rarely need to import from `vitest` directly.

### addEqualityTesters

Registers equality testers so Vitest's `expect(...).toEqual(...)` understands
Effect's `Equal` trait (e.g. two `Option`s or `Data` structs compare by value).
Call it once at module top level.

```ts
addEqualityTesters()

it("compares by Equal", () => {
  expect(Option.some(1)).toEqual(Option.some(1)) // => true via Equal.equals
})
```

### makeMethods

Builds the full set of Effect-aware methods (`effect`, `live`, `layer`,
`flakyTest`, `prop`, ...) from a custom Vitest `it`. Advanced — use it when you
have a specialized Vitest `TestAPI` and want the `@effect/vitest` surface on top
of it. `it` itself is `makeMethods(vitest.it)`.

```ts
const it = makeMethods(V.it) // => { effect, live, layer, flakyTest, prop, ... }
```

### describeWrapped

Wraps a Vitest `describe` block and hands the callback a fresh set of
Effect-aware methods built for that block. Advanced; useful for building custom
test harnesses.

```ts
describeWrapped("suite", (it) => {
  it.effect("runs", () => Effect.void) // => `it` is the Effect-aware methods
})
```

## Assertions reference (@effect/vitest/utils)

Value-level assertion helpers, imported from `@effect/vitest/utils`. They throw
on failure and are meant to be called *after* a test has produced a value;
they do not run Effects or advance the `TestClock`.

```ts
```

### General assertions

#### fail

Fails the current test with the given message.

```ts
if (false) fail("unreachable branch was reached") // => throws AssertionError
```

#### strictEqual

Reference / primitive equality via Node's `assert.strictEqual`.

```ts
strictEqual(1 + 1, 2) // => passes
```

#### deepStrictEqual

Deep structural equality via Node's `assert.deepStrictEqual`.

```ts
deepStrictEqual({ a: 1 }, { a: 1 }) // => passes
```

#### notDeepStrictEqual

Asserts two values are **not** deeply equal.

```ts
notDeepStrictEqual({ a: 1 }, { a: 2 }) // => passes
```

#### assertEquals

Equality using Effect's `Equal` trait (falls back to a `deepStrictEqual` diff on
failure so Vitest can show a structural diff).

```ts
assertEquals(Option.some(1), Option.some(1)) // => passes via Equal.equals
```

#### assertTrue / assertFalse

Assert a value is exactly `true` / `false`.

```ts
assertTrue(1 < 2) // => passes
assertFalse(1 > 2) // => passes
```

#### assertInclude

Asserts a string includes a substring.

```ts
assertInclude("hello world", "world") // => passes
```

#### assertMatch

Asserts a string matches a regular expression.

```ts
assertMatch("2026-05-30", /^\d{4}-\d{2}-\d{2}$/) // => passes
```

#### assertInstanceOf

Asserts a value is an instance of a constructor (and narrows its type).

```ts
assertInstanceOf(new Error("boom"), Error) // => passes, narrows to Error
```

#### doesNotThrow

Asserts a thunk does not throw.

```ts
doesNotThrow(() => JSON.parse("{}")) // => passes
```

#### throws

Asserts a thunk throws, optionally checking the thrown value against an `Error`
or a validation function.

```ts
throws(() => JSON.parse("{")) // => passes; threw a SyntaxError
```

#### throwsAsync

Like `throws`, but for a thunk returning a rejected promise (the only async
helper here).

```ts
await throwsAsync(() => Promise.reject(new Error("nope"))) // => passes
```

### Option assertions

#### assertSome

Asserts an `Option` is `Some` holding a value equal to `expected`.

```ts
assertSome(Option.some(42), 42) // => passes
```

#### assertNone

Asserts an `Option` is `None`.

```ts
assertNone(Option.none()) // => passes
```

#### assertDefined / assertUndefined

Assert a value is (not) `undefined`; `assertDefined` narrows the type.

```ts
assertDefined(1 as number | undefined) // => passes, narrows to number
assertUndefined(undefined) // => passes
```

### Result assertions

#### assertSuccess

Asserts a `Result` is `Success` holding a value equal to `expected`.

```ts
assertSuccess(Result.succeed(1), 1) // => passes
```

#### assertFailure

Asserts a `Result` is `Failure` holding an error equal to `expected`.

```ts
assertFailure(Result.fail("boom"), "boom") // => passes
```

### Exit assertions

#### assertExitSuccess

Asserts an `Exit` is a success with a value equal to `expected`. Pair it with
`Effect.exit` to capture an Effect's outcome.

```ts
it.effect("succeeds", () =>
  Effect.gen(function*() {
    const exit = yield* Effect.exit(Effect.succeed(1))
    assertExitSuccess(exit, 1) // => passes
  }))
```

#### assertExitFailure

Asserts an `Exit` is a failure with a `Cause` equal to `expected`.

```ts
it.effect("fails", () =>
  Effect.gen(function*() {
    const exit = yield* Effect.exit(Effect.fail("boom"))
    assertExitFailure(exit, Cause.fail("boom")) // => passes
  }))
```

The `assert` namespace exported from `@effect/vitest` also re-exports Vitest's
own `assert` (`assert.strictEqual`, `assert.deepStrictEqual`, `assert.isTrue`,
`assert.instanceOf`, ...), so for the most common checks you can use the single
`@effect/vitest` import and reach for `@effect/vitest/utils` when you need the
Effect-aware helpers above.

## In this section

- **[Writing tests](https://effect.plants.sh/testing/writing-tests/)** — use `it.effect` to run
  Effects, assert on results and failures, and parameterize tests with `each`
  and property-based testing.
- **[TestClock](https://effect.plants.sh/testing/testclock/)** — drive sleeps, timeouts, retries, and
  schedules forward in virtual time so time-dependent code runs instantly and
  deterministically.
- **[Testing services](https://effect.plants.sh/testing/testing-services/)** — provide test
  implementations of your [services](https://effect.plants.sh/services-and-layers/) as layers, share a
  layer across a block with `layer(...)`, and inspect state through a test ref.
- **[Integration testing](https://effect.plants.sh/testing/integration-testing/)** — run your real
  services wired together and swap only the lowest-level dependency (e.g. back
  the whole stack with in-memory SQLite instead of production Postgres).
- **[Schema and property testing](https://effect.plants.sh/testing/schema-and-property-testing/)** —
  generate inputs from `Schema` arbitraries with `it.effect.prop`, and validate
  schemas round-trip with `TestSchema` and `FastCheck`.