# @svelte-lean/query/testing

> createTestClient(), mock, reply, route(), mswHandlers(), fakeSource(), memoryStore() and persistStoreSuite().

Page: https://svelte-lean.vercel.app/query/api/testing · Markdown: https://svelte-lean.vercel.app/query/api/testing.md · Index: https://svelte-lean.vercel.app/llms.txt

createTestClient(): a client with a virtual clock, a deterministic transport, typed mocks and a call log; the same typed handlers serve MSW; doubles for live sources and persistence stores.

## Import

`import`

```ts
import { createTestClient, reply, route, mswHandlers } from '@svelte-lean/query/testing';
```

## Usage

`src/lib/api/members.test.ts`

```ts
// src/lib/api/members.test.ts
import { createMutation, createQuery } from '@svelte-lean/query';
import { createTestClient, reply } from '@svelte-lean/query/testing';
import { expect, test } from 'vitest';
import { createMember, listMembers } from '$api/members';

test('a refused invite marks the field and refetches nothing', async () => {
	const t = createTestClient();
	t.mock(listMembers, { rows: [], total: 0, page: 1, perPage: 20, lastPage: 1 }, { latency: 50 });
	t.mock(createMember, () => reply.validation({ email: 'taken' }));

	const list = createQuery(listMembers, () => ({}));
	const save = createMutation(createMember);
	t.observe(() => list.data);
	await t.advance(50);

	await save
		.run({ name: 'Mei', email: 'mei@harbor.studio', team: 'Design', role: 'member' })
		.catch(() => {});
	expect(save.error?.fields?.fields.email).toEqual(['taken']);
	expect(t.calls(listMembers)).toHaveLength(1);
	expect(t.findings()).toEqual([]);
	t.dispose();
});
```

## Size

Production Vite builds of a small consumer entry, Svelte external. A marginal row is the bytes the entry adds over the row it names.

| Build | Over | gzip | brotli | Budget (gzip) |
| --- | --- | --- | --- | --- |
| `testing (not shipped)` | – | 18878 B | 17235 B | 19300 B |

Read from `packages/query/artifacts/size.json`. Every entry and its method: [Performance](https://svelte-lean.vercel.app/query/performance.md).

## Functions

Test clients, replies, MSW interop and doubles.

### Exports

Runtime exports of the entry.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| # `createTestClient` | `(init?: TestClientInit) => TestClient` | – | A fresh realm client per test; dispose it with using or t.dispose(). |
| # `mock` | `TestClient['mock']` | – | t.mock on the installed test client. |
| # `reply` | `object` | – | Scripted answers: errors of each kind, raw bodies, meta, sequences, deferred and never. |
| # `route` | `(endpoint, handler, options?: MockOptions) => MswRoute` | – | A typed handler for one endpoint, for mswHandlers() or t.mock(route). |
| # `mswHandlers` | `(http: MswHttpLike, base: string, routes: MswRoute[], options?: { profile? }) => unknown[]` | – | MSW handlers built with the application's own msw http namespace; answers go through the profile. |
| # `prng` | `(seed: number) => () => number` | – | A seeded random function (retry jitter in tests). |
| # `fakeSource` | `(options?: { ready?: 'auto' \| 'manual' }) => FakeLiveSource` | – | A LiveSource double: emit events, confirm subscriptions, report the connection. |
| # `memoryStore` | `() => PersistStore` | – | A PersistStore in memory. |
| # `persistStoreSuite` | `(factory) => PersistStoreCase[]` | – | The contract cases a PersistStore must pass; run them in any test runner. |

### reply

Members of reply; a handler returns one instead of a value.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| # `network` | `() => Reply` | – | A network failure. |
| # `timeout` | `() => Reply` | – | A timeout. |
| # `http` | `(status, init?: { message?, code?, retryAfter? }) => Reply` | – | An HTTP error; 401 and 403 are forbidden, 422 validation. |
| # `validation` | `(fields, message?) => Reply` | – | A 422 with field messages typed by the mutation body. |
| # `refused` | `(init?: { code?, message? }) => Reply` | – | An application refusal inside a 200. |
| # `forbidden` | `(status?: 401 \| 403) => Reply` | – | Access withdrawn. |
| # `parse` | `() => Reply` | – | An answer that fails its schema. |
| # `never` | `() => Reply` | – | No answer at all. |
| # `meta` | `(value, meta: ResponseMeta) => Reply` | – | A value with response meta (server-driven freshness). |
| # `raw` | `(status, body, headers?) => Reply` | – | A raw body read through the profile. |
| # `sequence` | `(...steps) => handler` | – | One step per call; the last repeats. |
| # `deferred` | `() => Deferred` | – | A handler the test resolves or rejects by hand. |

## Options

TestClientInit and the mock options.

### TestClientInit

Every ClientInit option except transport, clock, environment and parts, plus these.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| # `parts` | `EnginePart[] \| 'lazy'` | `allParts` | Every engine part is included up front (no loading step on the virtual clock); 'lazy' tests the path where each part arrives after the engine first asks for it. |
| # `clock` | `'virtual' \| 'real'` | `'virtual'` | real turns latency into real time (demos). |
| # `start` | `number` | `Date.UTC(2026, 0, 1)` | Virtual epoch. |
| # `seed` | `number` | `1` | PRNG seed for retry jitter. |
| # `unhandled` | `'error' \| 'pending'` | `'error'` | What a request with no mock does. |
| # `visible` | `boolean` | `true` | Initial visibility. |
| # `online` | `boolean` | `true` | Initial network state. |
| # `profile` | `Profile` | – | Wire mode: mocks answer server-shaped bodies through the profile. |
| # `ssr` | `boolean` | – | Server mode: no realm client, inert scheduler, no retries. |

### MockOptions

The third argument of t.mock() and route().

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| # `latency` | `Duration \| ((call) => Duration)` | `0` | Virtual latency. |
| # `when` | `(params) => boolean` | – | Only answer matching calls. |
| # `times` | `number` | – | Then fall back to the previous mock. |
| # `ignoreAbort` | `boolean` | – | Resolve even after abort (tests the sequence guard). |

## Test client

TestClient extends Client with these members.

### TestClient

Time moves only when the test moves it.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| # `mock` | `(endpoint, handler, options?) => void` | – | Answer an endpoint: a value, a reply, or a function of the call; typed by the endpoint. |
| # `calls` | `(endpoint?) => Call[]` | – | The call log, of one endpoint or all. |
| # `pending` | `() => PendingCall[]` | – | Calls without an answer yet. |
| # `advance` | `(time: Duration) => Promise<void>` | – | Move the virtual clock and settle what is due. |
| # `flush` | `() => Promise<void>` | – | Settle what is due now. |
| # `now` | `() => number` | – | The virtual time. |
| # `observe` | `(read: () => unknown) => () => void` | – | Read a result in a reactive context, as a component would. |
| # `seedEntry` | `(endpoint, params, value, options?) => void` | – | Put a value in the cache without a request. |
| # `hide` | `() => void` | – | The document becomes hidden. |
| # `show` | `() => void` | – | The document becomes visible. |
| # `disconnect` | `() => void` | – | The network goes offline. |
| # `reconnect` | `() => void` | – | The network comes back. |
| # `findings` | `() => Finding[]` | – | Development findings; devtools() runs in every test client. |
| # `dispose` | `() => void` | – | Stop the client. |

### Call

One entry of the call log.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| # `params` | `P` | – | The params or input. |
| # `cause` | `Cause \| 'mutation'` | – | Why it was sent. |
| # `priority` | `'high' \| 'auto' \| 'low'` | – | Fetch priority. |
| # `attempt` | `number` | – | 1, then retries. |
| # `scope` | `Scope` | – | The scope. |
| # `sentAt` | `number` | – | Virtual send time. |
| # `settledAt` | `number \| undefined` | – | Virtual settle time. |
| # `outcome` | `'pending' \| 'success' \| 'error' \| 'aborted'` | – | How it ended. |
| # `error` | `QueryError \| undefined` | – | The error, when it failed. |

## Guide

How to use it, with examples: [Testing](https://svelte-lean.vercel.app/query/testing.md). All entry points: [Svelte Lean Query](https://svelte-lean.vercel.app/query.md).
