# Testing Services

Real applications are built from [services](https://effect.plants.sh/services-and-layers/) — a database
repository, an HTTP client, a clock. In tests you rarely want the real ones;
you want fast, in-memory implementations you can control and inspect. Because
Effect wires dependencies through `Layer`, swapping a service for a test version
is just providing a different layer. Nothing in the code under test changes.

The pattern below defines a `TodoRepo` service with two layers that share one
interface: a production `layer` backed by a `Database`, and a `layerTest` that
stores todos in a `Ref`. The code under test only ever sees the `TodoRepo`
interface, so swapping one layer for the other changes nothing about it. The
test ref is itself a small service, so a test can reach in and assert on the
underlying state directly.

```ts
import { assert, describe, it, layer } from "@effect/vitest"
import { Array, Context, Effect, Layer, Ref } from "effect"

interface Todo {
  readonly id: number
  readonly title: string
}

// An external dependency the *production* repo talks to. The real layer opens a
// connection pool — elided here, since the focus is the test layer. Note this is
// the only place a database appears: the test layer never touches it.
class Database extends Context.Service<Database, {
  query<A>(sql: string, params: ReadonlyArray<unknown>): Effect.Effect<ReadonlyArray<A>>
}>()("app/Database") {
  static readonly layer = Layer.succeed(Database, {
    query: () => Effect.die("not implemented in this example")
  })
}

// A tiny service that just holds the in-memory store used by the *test* layer.
// Exposing it as its own service lets tests inspect the raw data when needed.
class TodoRepoTestRef extends Context.Service<TodoRepoTestRef, Ref.Ref<Array<Todo>>>()(
  "app/TodoRepoTestRef"
) {
  static readonly layer = Layer.effect(TodoRepoTestRef, Ref.make(Array.empty<Todo>()))
}

// The service under test. The interface is the contract; everything below is two
// implementations of it that the tests can use interchangeably.
class TodoRepo extends Context.Service<TodoRepo, {
  create(title: string): Effect.Effect<Todo>
  readonly list: Effect.Effect<ReadonlyArray<Todo>>
}>()("app/TodoRepo") {
  // Production: SQL against the real Database. `Layer.provide` consumes the
  // Database dependency and *hides* it — the resulting layer provides TodoRepo
  // and nothing else, because the database is an internal implementation detail.
  static readonly layer = Layer.effect(
    TodoRepo,
    Effect.gen(function*() {
      const db = yield* Database

      const create = Effect.fn("TodoRepo.create")(function*(title: string) {
        const rows = yield* db.query<Todo>(
          "INSERT INTO todos (title) VALUES ($1) RETURNING id, title",
          [title]
        )
        return rows[0]
      })

      const list = db.query<Todo>("SELECT id, title FROM todos ORDER BY id", [])

      return TodoRepo.of({ create, list })
    })
  ).pipe(Layer.provide(Database.layer))

  // Test: the *same* interface, no database — state lives in the Ref. The bodies
  // differ, the contract does not. `provideMerge` supplies the test ref AND keeps
  // it visible, so a test can yield* TodoRepoTestRef to inspect the store directly.
  static readonly layerTest = Layer.effect(
    TodoRepo,
    Effect.gen(function*() {
      const store = yield* TodoRepoTestRef

      const create = Effect.fn("TodoRepo.create")(function*(title: string) {
        const todos = yield* Ref.get(store)
        const todo = { id: todos.length + 1, title }
        yield* Ref.set(store, [...todos, todo])
        return todo
      })

      const list = Ref.get(store)

      return TodoRepo.of({ create, list })
    })
  ).pipe(Layer.provideMerge(TodoRepoTestRef.layer))
}

describe("TodoRepo", () => {
  it.effect("starts empty and records created todos", () =>
    Effect.gen(function*() {
      const repo = yield* TodoRepo
      assert.strictEqual((yield* repo.list).length, 0)

      yield* repo.create("Write docs")

      const todos = yield* repo.list
      assert.strictEqual(todos.length, 1)
      assert.strictEqual(todos[0].title, "Write docs")
    }).pipe(Effect.provide(TodoRepo.layerTest)))
})
```

## Providing a test layer

The test above ends with `Effect.provide(TodoRepo.layerTest)`, satisfying the
`TodoRepo` requirement on the test Effect with the in-memory layer. Because the
service contract (`create`, `list`) is identical to production, the exact same
test body would validate the real implementation if you provided `TodoRepo.layer`
instead — only the layer changes, never the test. Giving each service a `layer`
and a `layerTest` side by side, both pointing at one interface, is the convention
this page follows.

## `Layer.provide` vs `Layer.provideMerge`

The two layers above each wire in a dependency, but they use different
combinators. The choice is about **visibility** — whether the dependency stays
reachable in the layer's output.

- **`Layer.provide(dep)`** feeds `dep` into the layer being built and then
  *hides* it. The production `TodoRepo.layer` uses it for `Database`: the database
  is an internal implementation detail, so the resulting layer provides `TodoRepo`
  and nothing else. Code that provides this layer can reach `TodoRepo` but not
  `Database`.
- **`Layer.provideMerge(dep)`** feeds `dep` in *and keeps it in the output*. The
  test layer uses it for `TodoRepoTestRef`: the resulting layer provides both
  `TodoRepo` **and** `TodoRepoTestRef`, so a test can `yield* TodoRepoTestRef` to
  inspect the raw store (see [Inspecting state through a test
  ref](#inspecting-state-through-a-test-ref) below).
**Tip:** Rule of thumb: use `provide` for real dependencies callers should not touch,
  and `provideMerge` in test layers when you deliberately want to reach a lower
  level to make assertions. Reaching for `provideMerge` everywhere leaks internals
  into the public surface of your layers.

## Sharing a layer across a block with `layer(...)`

Providing the layer per test gives each test a **fresh** instance — a clean
store every time. Sometimes you want the opposite: one instance shared across a
group of tests, built once and torn down in `afterAll`. The `layer(...)` helper
from `@effect/vitest` does exactly that, and hands you an `it` already scoped to
that layer's services.

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

// `TodoRepo.layerTest` is built once for the whole block.
layer(TodoRepo.layerTest)("TodoRepo (shared)", (it) => {
  it.effect("creates the first todo", () =>
    Effect.gen(function*() {
      const repo = yield* TodoRepo
      assert.strictEqual((yield* repo.list).length, 0)
      yield* repo.create("Write docs")
      assert.strictEqual((yield* repo.list).length, 1)
    }))

  it.effect("sees state from the previous test", () =>
    Effect.gen(function*() {
      const repo = yield* TodoRepo
      // The layer is shared, so the todo from the previous test is still here.
      assert.strictEqual((yield* repo.list).length, 1)
      yield* repo.create("Write docs again")
      assert.strictEqual((yield* repo.list).length, 2)
    }))
})
```
**Caution:** A shared layer means shared state. The second test above relies on the first
  having run — order matters, and these tests are no longer independent. Prefer
  per-test `Effect.provide` when you want isolation; reach for `layer(...)` when
  setup is expensive (a connection pool, a container) and you deliberately want
  to reuse it.

## Inspecting state through a test ref

Because the store is exposed as the `TodoRepoTestRef` service, a test can read
the raw `Ref` to make assertions the public interface does not surface. This is
useful for verifying side effects — that a write happened, a cache was
populated, an event was recorded — without adding test-only methods to the
production interface.

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

describe("inspecting the store", () => {
  it.effect("writes land in the underlying ref", () =>
    Effect.gen(function*() {
      const repo = yield* TodoRepo
      const ref = yield* TodoRepoTestRef // the shared store, surfaced by provideMerge

      yield* repo.create("Review docs")

      // Assert against the raw data rather than only the public `list`.
      const todos = yield* Ref.get(ref)
      assert.strictEqual(todos.length, 1)
      assert.strictEqual(todos[0].title, "Review docs")
    }).pipe(Effect.provide(TodoRepo.layerTest)))
})
```

## Layering services on top of each other

Higher-level services depend on lower-level ones. A `TodoService` that depends
on `TodoRepo` gets a test layer by providing the repo's *test* layer to it.
Using `Layer.provideMerge` keeps the repo (and, through it, the test ref)
reachable from tests as well, so you can assert at any level of the stack.

```ts
class TodoService extends Context.Service<TodoService, {
  addAndCount(title: string): Effect.Effect<number>
}>()("app/TodoService") {
  static readonly layerTest = Layer.effect(
    TodoService,
    Effect.gen(function*() {
      const repo = yield* TodoRepo

      const addAndCount = Effect.fn("TodoService.addAndCount")(function*(title: string) {
        yield* repo.create(title)
        const todos = yield* repo.list
        return todos.length
      })

      return TodoService.of({ addAndCount })
    })
    // Provide the repo's test layer as the dependency, and keep it (plus the
    // test ref) merged into the context so tests can reach every level.
  ).pipe(Layer.provideMerge(TodoRepo.layerTest))
}

describe("TodoService", () => {
  it.effect("delegates to the repo and counts", () =>
    Effect.gen(function*() {
      const service = yield* TodoService
      const count = yield* service.addAndCount("Review docs")
      assert.strictEqual(count, 1)

      // Still able to inspect the lowest-level store, thanks to provideMerge.
      const ref = yield* TodoRepoTestRef
      assert.strictEqual((yield* Ref.get(ref)).length, 1)
    }).pipe(Effect.provide(TodoService.layerTest)))
})
```

When the service under test depends on time, combine these test layers with the
[TestClock](https://effect.plants.sh/testing/testclock/) — under `it.effect` it is already provided, so
sleeps and schedules inside your services stay deterministic too.

The approach here fakes each service in turn. The opposite strategy — keep every
service *real* and swap only the lowest-level dependency (a database, an HTTP
client) — is covered in [Integration testing](https://effect.plants.sh/testing/integration-testing/).