# Writing Tests

The core idea of `@effect/vitest` is that a test *returns an Effect*. You write
the body with `Effect.gen`, `yield*` the effects you want to exercise, and use
`assert` to check the values. `it.effect` runs the Effect, fails the test if it
fails unexpectedly, provides the test services (`TestClock`, `TestConsole`), and
closes the test [Scope](https://effect.plants.sh/resource-management/) afterwards.

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

// The code under test: a small Effect-returning function.
const divide = (a: number, b: number): Effect.Effect<number, "DivByZero"> =>
  b === 0 ? Effect.fail("DivByZero") : Effect.succeed(a / b)

describe("divide", () => {
  // The body is an Effect. `yield*` runs the effect under test, then we
  // assert on the value it produced.
  it.effect("returns the quotient on success", () =>
    Effect.gen(function*() {
      const result = yield* divide(10, 2)
      assert.strictEqual(result, 5)
    }))
})
```

## Assertions

Import `assert` from `@effect/vitest`. It re-exports Vitest's `assert`, so all
the familiar checks are available:

```ts
import { assert } from "@effect/vitest"

assert.strictEqual(1 + 1, 2) //               reference / primitive equality
assert.deepStrictEqual([1, 2], [1, 2]) //     structural equality
assert.isTrue([1, 2].includes(1)) //          boolean checks
assert.include("hello world", "world") //     substring / element membership
```
**Caution:** Use `assert` from `@effect/vitest`, not `expect` from Vitest. The Effect
  testing conventions standardize on `assert`, and `@effect/vitest` registers
  Effect-aware equality testers so values implementing the `Equal` trait
  compare correctly.

For Effect data types there are dedicated helpers in `@effect/vitest/utils` that
give clearer failure messages than unwrapping by hand — `assertSome` /
`assertNone` for `Option`, `assertSuccess` / `assertFailure` for
[`Result`](https://effect.plants.sh/data-types/), and `assertExitSuccess` / `assertExitFailure` for
`Exit`.

## Testing failures

An Effect that *fails* would fail the surrounding test, which is not what you
want when the failure is the expected outcome. Capture the outcome with
`Effect.exit` (or `Effect.result`) and assert on it instead.

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

const divide = (a: number, b: number): Effect.Effect<number, "DivByZero"> =>
  b === 0 ? Effect.fail("DivByZero") : Effect.succeed(a / b)

describe("divide failures", () => {
  it.effect("fails with DivByZero when dividing by zero", () =>
    Effect.gen(function*() {
      // `Effect.exit` turns failure into a value, so the test does not abort.
      const exit = yield* Effect.exit(divide(1, 0))
      assert.deepStrictEqual(exit, Exit.fail("DivByZero"))
    }))
})
```

`Exit` distinguishes an expected `fail` (your typed error channel) from a
`die` (an unexpected defect). Asserting on the whole `Exit` therefore checks not
just *that* it failed but *how* it failed.

## Parameterized tests with `each`

`it.effect.each` runs the same Effect body once per row in a table — ideal for
table-driven tests. The `%#` placeholder in the test name is replaced with the
case index.

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

describe("normalize", () => {
  it.effect.each([
    { input: " Ada ", expected: "ada" },
    { input: " Lin ", expected: "lin" },
    { input: " Nia ", expected: "nia" }
  ])("trims and lowercases case %#", ({ input, expected }) =>
    Effect.gen(function*() {
      assert.strictEqual(input.trim().toLowerCase(), expected)
    }))
})
```

## Property-based testing

`it.effect.prop` generates inputs and runs the Effect body many times with
different values, shrinking to a minimal counterexample on failure. Inputs are
described with [`Schema`](https://effect.plants.sh/schema/), which doubles as an arbitrary generator.

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

describe("string properties", () => {
  // For each generated string, reversing it twice must be the identity.
  it.effect.prop(
    "reversing twice is identity",
    [Schema.String],
    ([value]) =>
      Effect.gen(function*() {
        const reversedTwice = value.split("").reverse().reverse().join("")
        assert.strictEqual(reversedTwice, value)
      })
  )
})
```

## `it.effect` vs `it.live`

`it.effect` runs your test against the **test services**: a `TestClock` whose
time only advances when you tell it to, and a `TestConsole` that captures output
instead of printing it. This is what makes time-dependent tests deterministic
(see [TestClock](https://effect.plants.sh/testing/testclock/)).

Occasionally you genuinely want the real runtime — the system clock, real
delays. Use `it.live` for those cases.

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

describe("live runtime", () => {
  // `it.live` uses the real Clock, so `Effect.sleep` actually waits.
  it.live("sleeps against the real clock", () =>
    Effect.gen(function*() {
      const before = yield* Clock.currentTimeMillis
      yield* Effect.sleep("10 millis")
      const after = yield* Clock.currentTimeMillis
      assert.isTrue(after - before >= 10)
    }))
})
```
**Note:** Avoid `Date.now()` and `new Date()` in code under test — read the time through
  `Clock` instead. Under `it.effect` the `Clock` is the `TestClock` and starts
  at the Unix epoch, so time-based logic stays deterministic.

## Flaky tests

For tests that depend on something genuinely non-deterministic, wrap the body
with `it.flakyTest`, which retries the Effect until it succeeds or a timeout
elapses.

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

describe("occasionally flaky", () => {
  it.effect("eventually rolls a six", () =>
    // Retry the Effect for up to 1 second until it succeeds.
    it.flakyTest(
      Effect.gen(function*() {
        // `nextIntBetween` is inclusive on both bounds in v4, so this is 1-6.
        const roll = yield* Random.nextIntBetween(1, 6)
        // Fail unless we rolled a six — `flakyTest` will retry on failure.
        yield* Effect.filterOrFail(
          Effect.succeed(roll),
          (n) => n === 6,
          () => "not a six" as const
        )
      }),
      "1 second"
    ))
})
```

Reach for `flakyTest` sparingly — most of the time the better fix is to make the
test deterministic with a `TestClock`, a seeded `Random`, or a test layer rather
than to retry it.