---
title: "Testing"
url: "https://effect-auth.itsbroly.com/guides/testing/"
description: "Test authentication services, operations, and HTTP APIs."
---



Authentication tests should become more realistic as they move outward, without making every assertion depend on a server or database. Keep time, randomness, storage, and network boundaries explicit so failures remain reproducible.

```text
             / browser journeys \       few: WebAuthn, redirects, cookies
            / HTTP API + storage  \      schemas, middleware, transactions
           / HTTP Operations       \     application boundary and policy
          / primitives              \    many: deterministic service tests
```

## Test matrix [#test-matrix]

| Layer                       | Provide                                                       | Assert                                                                                                      |
| --------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| Primitives                  | In-memory stores, `TestClock`, deterministic crypto           | Success and typed failures, expiry boundaries, continuation state, one-time consumption                     |
| HTTP Operations / `HttpApi` | Substitute operation services, then build selected API groups | Inputs reach the operation once; status, error schema, headers, origin/CSRF policy, and cookies are correct |
| Browser client              | Stub `fetch` and browser credential APIs                      | URLs and encoded bodies, passkey start/finish sequencing, typed API errors, disposal                        |
| Storage adapters            | The real database engine and transaction mode                 | Round trips, ordering, `Option` semantics, rollback, atomic consume/claim/CAS                               |
| Concurrency and security    | Parallel fibers or requests with shared real state            | Exactly one winner, replay rejection, rate-limit accounting, revocation and rotation behavior               |

Most feature behavior belongs in primitive tests. Add an Operations test when your application substitutes or wraps an exported `*HttpOperations` service. Add an `HttpApi` test for transport behavior: schema decoding, middleware, response mapping, and the complete `Set-Cookie` value. Reserve browser tests for behavior that only exists around `fetch`, WebAuthn conversion, redirects, or shared client state.

## Deterministic services [#deterministic-services]

`@effect-auth/core/Testing` publicly exports `makeTestCrypto`, `CryptoTestLive`, `makeTestPasswordHasher`, `PasswordHasherTestLive`, and `CoreTestingLive`. The merged layer also supplies no-op audit logging and `WaitUntil`, plus test privacy services. These implementations are predictable, not secure; never provide them in production.

```ts
import { expect, it } from "@effect/vitest";
import { Crypto } from "@effect-auth/core/Crypto";
import { CryptoTestLive } from "@effect-auth/core/Testing";
import { Effect } from "effect";

it.effect("issues reproducible tokens", () =>
  Effect.gen(function* () {
    const crypto = yield* Crypto;
    expect(yield* crypto.randomToken(16)).toBe("test-token-16-1");
    expect(yield* crypto.randomToken(16)).toBe("test-token-16-2");
  }).pipe(Effect.provide(CryptoTestLive))
);
```

Use a fresh `makeTestCrypto()` when a test needs an isolated counter. Use `CoreTestingLive` when the subject requires several core collaborators, then provide feature stores and policies separately. Control deadlines with Effect's `TestClock`; set an exact epoch and test immediately before, at, and after expiry rather than sleeping.

## HTTP and browser boundaries [#http-and-browser-boundaries]

For an Operations test, construct the public service with `PasswordHttpOperations.of(...)` (or the relevant feature), provide it with `Layer.succeed`, and build the exported API group. This proves a built-in or [Custom API](/guides/custom-auth-api/) binds your service without requiring real credentials or persistence.

At the API layer, assert the wire contract rather than only the decoded success value. Cover malformed payloads and the stable error body described by the [Error Model](/concepts/error-model/). For session, refresh, approval, and trusted-device flows, carry request cookies between calls and assert cookie name, path, `HttpOnly`, `Secure`, `SameSite`, expiry, and clearing behavior. Continuation flows must reject a missing, wrong-device, expired, or already-consumed continuation.

Browser client tests can inject `fetch` through `createAuthClient`. Return real `Response` objects, record each `Request`, and stub `CredentialsContainer.create`/`get` for passkeys. Check base64url conversion and start/finish order, then call `client.dispose()`. Keep one small real-server journey to catch header handling that a fetch stub cannot model.

## Storage and adversarial cases [#storage-and-adversarial-cases]

Run adapter tests against the production engine, not an in-memory imitation. In addition to ordinary CRUD, launch concurrent attempts against verification codes, recovery codes, OAuth grants, refresh rotation, passkey counters, and webhook claims. Assert exactly the contractually allowed number succeed and that losers do not mutate state. Rate-limit tests should verify the first allowed decision, the denied decision, window reset under controlled time, key isolation, and that middleware does not consume budget twice.

The repository has broad internal runners in `packages/core/test/StorageContract.ts`, but **they are not public exports**. Do not import repository test files or `dist` internals. Treat those cases as an executable specification and reproduce the relevant semantics in your adapter suite. See [Custom Database](/storage/custom-database/) for atomicity, exact expiry boundaries, and the full adapter checklist.

