>()
fn.setup.toRejectInOrder(new Error('1'), new Error('2'))
fn.setup.toResolveWith('ok')
const p = withRetry(fn, 5, 100)
// Exhaust retries
for (let i = 0; i < 5; i++) {
await clock.flushMicrotasks()
clock.tick(100)
}
expect(await p).toBe('ok')
fn.expect.called.times(3) // two rejects + one resolve
} finally {
clock.restore()
}
```
## Pattern: assert a setInterval polling
```typescript
function startPolling(fn: () => void, ms: number) {
return setInterval(fn, ms)
}
const clock = useFakeTimers()
try {
const fn = func<() => void>()
const handle = startPolling(fn, 50)
clock.tick(50) // fires 1
clock.tick(50) // fires 2
clock.tick(25) // nothing yet
clock.tick(25) // fires 3
fn.expect.called.times(3)
clearInterval(handle)
} finally {
clock.restore()
}
```
::: warning runAll() with intervals
`clock.runAll()` throws when its 10 000-iteration guard trips — which it will with an active `setInterval`. Always `clearInterval` before `runAll`, or use `tick(ms)` explicitly.
:::
## Pattern: freeze Date.now for time-stamp-dependent tests
```typescript
const clock = useFakeTimers(new Date('2024-01-15T10:30:00Z').getTime())
try {
const record = createRecord()
expect(record.createdAt).toBe(1705314600000)
} finally {
clock.restore()
}
```
Or step the clock between operations:
```typescript
const clock = useFakeTimers(0)
try {
const a = createRecord()
clock.tick(1000)
const b = createRecord()
expect(b.createdAt - a.createdAt).toBe(1000)
} finally {
clock.restore()
}
```
## Pattern: fire microtasks deterministically
```typescript
const clock = useFakeTimers()
try {
let resolved = false
Promise.resolve().then(() => { resolved = true })
// Microtasks queued via the real Promise aren't caught by the fake clock —
// they fire on the runtime's own microtask queue. But code that uses
// queueMicrotask() IS captured:
let via = false
queueMicrotask(() => { via = true })
clock.flushMicrotasks()
expect(via).toBe(true)
} finally {
clock.restore()
}
```
::: tip Scope of microtask control
`deride/clock` only patches `queueMicrotask` — Promise `.then` callbacks aren't intercepted. For full ordering control (Promise microtask queue + task queue together) reach for `vi.useFakeTimers()` or `@sinonjs/fake-timers`.
:::
## Pattern: accelerate a callback via `toTimeWarp`
For APIs whose internal timeout is awkward (e.g. a 30-second poll), `toTimeWarp(0)` schedules their found callback with a zero delay — no global clock needed:
```typescript
const client = wrap(realClient)
client.setup.pollWithRetry.toTimeWarp(0)
client.pollWithRetry(30_000, (result) => {
// runs immediately
})
```
This doesn't affect `Date.now` or other timers — it only shortcuts the callback deep inside the mocked method.
## Interop: vitest's fake timers
If you already use `vi.useFakeTimers()` in your project, you don't need `deride/clock` — vitest's timer advance resolves internal `setTimeout`s. But don't mix them:
```typescript
// ❌ Don't do this — two fake-timer systems fight
vi.useFakeTimers()
const clock = useFakeTimers() // deride clock throws: "already installed"
```
Pick one per test.
## See also
- [`setup.toResolveAfter` / `toRejectAfter` / `toHang`](../guide/configuring-behaviour#time-shifted-async)
- [`deride/clock`](../integrations/clock) — full API
- [Async & Promises recipes](./async-mocking)
──────────────────────────────────────────────────────────────────────────────
# /v2/
Source: https://guzzlerio.github.io/deride/deride/v2/index.md
──────────────────────────────────────────────────────────────────────────────
## At a glance
```typescript
import { stub, match } from 'deride'
interface Database {
query(sql: string): Promise
findById(id: number): Promise
}
const mockDb = stub(['query', 'findById'])
mockDb.setup.query.toResolveWith([{ id: 1, name: 'alice' }])
mockDb.setup.findById.when(match.gte(100)).toRejectWith(new Error('not found'))
const result = await mockDb.query('SELECT * FROM users')
mockDb.expect.query.called.once()
mockDb.expect.query.called.withArg(match.regex(/FROM users/))
mockDb.expect.findById.called.never()
```
**No monkey-patching.** `mockDb` is a wrapper around a fresh object. Your real `Database` class is untouched. That's why deride works on frozen objects, sealed classes, and any coding style.
──────────────────────────────────────────────────────────────────────────────
# For Agents
Source: https://guzzlerio.github.io/deride/deride/v2/ai/index.md
──────────────────────────────────────────────────────────────────────────────
**Context-dense docs for Claude, Cursor, Copilot, and other AI assistants writing code against deride.** Everything under `/ai/` is written for machine consumption first — terse, scannable, example-led — while still being useful to humans.
If you're a human reader looking for the full guide, head to [**Introduction**](../guide/introduction). If you're an agent or an agent-assisted developer, start here.
## What's in this section
| Page | Purpose |
|------|---------|
| [Decision tree](./decision-tree) | "Which factory / setup / expect do I use?" — flat tables, no prose. |
| [Canonical examples](./canonical-examples) | ONE blessed snippet per common task. Use these verbatim; they're the idiomatic shape. |
| [Common mistakes](./common-mistakes) | Anti-patterns and their fixes, phrased the way an agent is likely to produce them. |
| [Agent-ready feeds](./feeds) | `llms.txt`, `llms-full.txt`, per-page `.md` variants — the feeds you should point your agent at. |
## Quick agent primer
deride is a **TypeScript-first mocking library**. It wraps rather than monkey-patches, so it works with frozen objects, sealed classes, and any coding style.
Three factories:
```typescript
import { stub, wrap, func } from 'deride'
stub(['methods']) // build from method names
stub(existingInstance) // mirror an instance's methods
stub(MyClass) // walk a class prototype
wrap(realObject) // partial mock — real methods run by default
func() // standalone mocked function
```
Three facades on every mock:
```typescript
mock.setup.method.toReturn(...) // configure behaviour
mock.expect.method.called.once() // assert (throws)
mock.spy.method.lastCall.returned // inspect (reads data)
```
The full flavour — matchers, sandbox, inOrder, sub-paths — is in the [Guide](../guide/introduction). This section is the lean reference.
## Pointing your agent at these docs
Three options, from least to most context-hungry:
1. **Give the agent `/llms.txt`** — a short index of every page with one-line summaries and links to the `.md` variants. ~3 KB, fits in any context window.
2. **Give the agent `/llms-full.txt`** — every page's markdown concatenated. ~80 KB at the time of writing. Fits in any modern long-context window (Claude, GPT-4.1, Gemini 2.5, etc.).
3. **Let the agent fetch individual `.md` variants** by appending `.md` to any URL it encounters. e.g. [`/guide/quick-start.md`](../guide/quick-start.md), [`/ai/decision-tree.md`](./decision-tree.md).
Full feed URLs and example MCP / tool configurations live on [Agent-ready feeds](./feeds).
## Design principles for this section
1. **One idiomatic shape per task.** No "here are three ways…" — we pick one and document it. Agents generate from the patterns they see.
2. **Tables over prose.** Agents don't need prose to understand a rule; they need the rule.
3. **Negative examples alongside positive.** "Don't do X" is more useful than "do Y" alone because it steers generation away from common failure modes.
4. **No hidden context.** Everything referenced in an example is self-contained or linked.
5. **Conventional-commit language.** This reinforces the conventions used elsewhere in the project — useful because the agent will often be writing the commit message too.
## Contributing
If you're updating the main Guide or API reference, **also update the matching page here** when it changes a common pattern or introduces a new one. See the [Decision tree](./decision-tree) as the first thing to sync.
The `CLAUDE.md` at the repo root tells automated contributors (and Claude sessions) to keep this section current; human contributors should check `CONTRIBUTING.md`.
──────────────────────────────────────────────────────────────────────────────
# Canonical examples
Source: https://guzzlerio.github.io/deride/deride/v2/ai/canonical-examples.md
──────────────────────────────────────────────────────────────────────────────
One blessed idiomatic snippet per common task. **Use these verbatim**; they're the shape the library is designed around. If an agent-generated solution strays from these patterns, assume the agent is confused and redirect to the closest pattern here.
## 1. Mock an async service, inject, assert
**Task:** test a service that depends on a database.
```typescript
import { describe, it, expect } from 'vitest'
import { stub, match } from 'deride'
interface Database {
query(sql: string): Promise
findById(id: number): Promise
}
class UserService {
constructor(private db: Database) {}
async listActive() { return this.db.query("SELECT * FROM users WHERE active") }
}
describe('UserService', () => {
it('queries the active users', async () => {
const mockDb = stub(['query', 'findById'])
mockDb.setup.query.toResolveWith([{ id: 1, name: 'alice' }])
const service = new UserService(mockDb)
const users = await service.listActive()
expect(users).toHaveLength(1)
mockDb.expect.query.called.once()
mockDb.expect.query.called.withArg(match.regex(/active/i))
})
})
```
## 2. Different returns per call
**Task:** a paginated fetch where each call returns the next page.
```typescript
mock.setup.fetchPage.toResolveInOrder(
{ page: 1, rows: ['a'] },
{ page: 2, rows: ['b'] },
{ page: 3, rows: ['c'] },
)
// 4th call: sticky-last (page 3 again)
// OR explicit fallback:
mock.setup.fetchPage.toResolveInOrder(
[{ page: 1 }, { page: 2 }],
{ then: null },
)
```
## 3. Conditional behaviour on args
**Task:** return different data depending on what the code under test asks for.
```typescript
mock.setup.findById.when(1).toResolveWith({ id: 1, name: 'alice' })
mock.setup.findById.when(match.gte(9999)).toRejectWith(new Error('not found'))
// Default fallthrough — any other id returns undefined
```
## 4. Error / timeout paths
**Task:** verify the code handles a timeout or rejection correctly.
```typescript
// Rejected promise
mock.setup.fetch.toRejectWith(new Error('network'))
await expect(service.loadData()).rejects.toThrow('network')
// Never-settling promise — for testing timeout logic
mock.setup.fetch.toHang()
const result = await Promise.race([
service.loadData(),
new Promise((r) => setTimeout(() => r('TIMEOUT'), 100)),
])
```
## 5. Fluent / chainable API (query builder, etc.)
**Task:** mock a chain-of-methods API.
```typescript
interface Query {
where(s: string): Query
orderBy(s: string): Query
execute(): Promise
}
const q = stub(['where', 'orderBy', 'execute'])
q.setup.where.toReturnSelf()
q.setup.orderBy.toReturnSelf()
q.setup.execute.toResolveWith([{ id: 1 }])
const rows = await q.where('active').orderBy('name').execute()
```
## 6. Mock a constructor call (`new X(...)`)
**Task:** code under test does `new Database(conn)` and you need to intercept both the construction and the instance's methods.
```typescript
import { stub } from 'deride'
import { Database } from './database'
const MockedDb = stub.class(Database)
MockedDb.setupAll((inst) => inst.setup.query.toResolveWith([]))
// Substitute the constructor at import time — test-runner specific
vi.mock('./database', () => ({ Database: MockedDb }))
// ... run code under test ...
MockedDb.expect.constructor.called.withArg('my-conn-string')
MockedDb.instances[0].expect.query.called.once()
```
## 7. Partial mock — real methods by default, override one
**Task:** spy on some methods of a real object while keeping others running.
```typescript
const realLogger = { info: (m: string) => console.log(m), error: (m: string) => console.error(m) }
const wrapped = wrap(realLogger)
wrapped.info('hello') // runs real console.log
wrapped.setup.error.toDoThis(() => {}) // silence errors in the test
wrapped.error('boom') // no output; recorded
wrapped.expect.error.called.withArg('boom')
```
## 8. Standalone mocked function (callback or fetcher)
**Task:** the code under test takes a function as a parameter.
```typescript
import { func } from 'deride'
const onTick = func<(frame: number) => void>()
animator.subscribe(onTick)
animator.step()
animator.step()
onTick.expect.called.twice()
onTick.expect.invocation(0).withArg(0)
onTick.expect.invocation(1).withArg(1)
```
## 9. Cross-mock call ordering
**Task:** assert a specific sequence of calls across multiple mocks.
```typescript
import { inOrder } from 'deride'
await repository.load()
inOrder(
db.spy.connect,
db.spy.query,
logger.spy.info,
)
```
For strict interleave (no other calls on listed spies between them), use `inOrder.strict(...)`.
## 10. Read a captured return value forward
**Task:** the code returned a Promise; you want to await its settled value in the test.
```typescript
mock.setup.fetch.toResolveWith({ id: 1 })
mock.fetch('/x')
const data = await mock.spy.fetch.lastCall!.returned as Promise<{ id: number }>
expect(await data).toEqual({ id: 1 })
```
Note: `expect.called.withReturn({ id: 1 })` asserts the value was returned, but can't hand it back. Use `spy.lastCall.returned` when you need the value for further assertions.
## 11. Sandbox pattern for test lifecycle
**Task:** many mocks per test file, reset between tests, full restore at end.
```typescript
import { sandbox } from 'deride'
const sb = sandbox()
beforeEach(() => {
mockDb = sb.stub(['query'])
mockLog = sb.wrap(realLogger)
})
afterEach(() => sb.reset()) // call history cleared, setups preserved
afterAll(() => sb.restore()) // full wipe (setups + history)
```
## 12. Fake timers for delayed async
**Task:** test a retry loop with backoff.
```typescript
import { useFakeTimers, isFakeTimersActive, restoreActiveClock } from 'deride/clock'
import { afterEach } from 'vitest'
afterEach(() => { if (isFakeTimersActive()) restoreActiveClock() })
it('retries twice then succeeds', async () => {
const clock = useFakeTimers()
const fn = func<() => Promise>()
fn.setup.toRejectInOrder(new Error('1'), new Error('2'))
fn.setup.toResolveWith('ok')
const p = withRetry(fn, 5, 100)
for (let i = 0; i < 3; i++) {
await clock.flushMicrotasks()
clock.tick(100)
}
expect(await p).toBe('ok')
fn.expect.called.times(3)
})
```
## 13. Vitest / jest matcher sugar
**Task:** prefer framework-native assertion style.
```typescript
// test-setup.ts (referenced from vitest.config.ts setupFiles)
import 'deride/vitest'
// any test file:
import { stub, match } from 'deride'
const mock = stub(['handle'])
mock.handle(42)
expect(mock.spy.handle).toHaveBeenCalledOnce()
expect(mock.spy.handle).toHaveBeenCalledWith(match.number)
expect(mock.spy.handle).toHaveBeenLastCalledWith(42)
```
For `jest`: replace `'deride/vitest'` with `'deride/jest'`. Everything else is identical.
---
## What NOT to generate
- Don't `vi.spyOn(x, 'method')` and then try to use deride — pick one.
- Don't mutate `mock.spy.calls[i].args` and expect the mock to "react" — those are records, not controls.
- Don't `await mock.expect.x.called.withReturn(v)` — `expect` returns void, not a Promise.
- Don't squash-merge a deride release PR — see the [CLAUDE.md](https://github.com/guzzlerio/deride/blob/main/CLAUDE.md) at the repo root.
Full list of anti-patterns and their fixes: [Common mistakes](./common-mistakes).
──────────────────────────────────────────────────────────────────────────────
# Common mistakes
Source: https://guzzlerio.github.io/deride/deride/v2/ai/common-mistakes.md
──────────────────────────────────────────────────────────────────────────────
Anti-patterns agents tend to produce, and the correct shape. Each entry shows the **wrong** version first (the thing we see in the wild) and then the **right** version, with a short note on why.
## 1. Mixing deride with `vi.spyOn` / `jest.spyOn`
**❌ Wrong**
```typescript
const spy = vi.spyOn(realDb, 'query').mockResolvedValue([])
// ... then trying to use deride on the same object ...
realDb.expect.query.called.once() // TypeError: realDb.expect is undefined
```
**✅ Right**
```typescript
const mockDb = wrap(realDb)
mockDb.setup.query.toResolveWith([])
await mockDb.query('…')
mockDb.expect.query.called.once()
```
**Why:** pick one mocking tool per object. `vi.spyOn` monkey-patches the real object; deride composes a new object. Don't mix.
## 2. Mutating `spy.calls[i].args` and expecting it to stick
**❌ Wrong**
```typescript
mock.spy.greet.calls[0].args[0] = 'hacked' // pointless — calls are immutable records
mock.greet() // does nothing different
```
**✅ Right**
`spy.calls` is a read-only log. If you want the mock to return a different value, configure it via `setup.*`:
```typescript
mock.setup.greet.when('hacked').toReturn(…)
```
**Why:** `spy` is the **inspection** surface — it records what happened, it doesn't drive what happens next.
## 3. Awaiting `expect.*` calls
**❌ Wrong**
```typescript
await mock.expect.fetch.called.withReturn({ id: 1 }) // returns undefined; await is a no-op
```
**✅ Right**
`expect.*` assertions are **synchronous** and return `void`. Don't `await` them.
```typescript
mock.expect.fetch.called.withReturn({ id: 1 })
```
If you specifically need to await a captured Promise to check its settled value, use `spy`:
```typescript
mock.setup.fetch.toResolveWith({ id: 1 })
mock.fetch('/x')
const promise = mock.spy.fetch.lastCall!.returned as Promise<{ id: number }>
await expect(promise).resolves.toEqual({ id: 1 })
```
## 4. Using `@ts-ignore` or `@ts-expect-error` for intentional type escape hatches
**❌ Wrong**
```typescript
// @ts-ignore
mock.setup.fetch.toResolveWith(null) // suppresses the type error with no visible marker
```
**✅ Right**
```typescript
mock.setup.fetch.toResolveWith(null as any) // or: as unknown as string
```
**Why:** deride's idiomatic escape hatch is the `as any` / `as unknown as T` cast. It's grep-able, localised to the one call, and doesn't suppress *other* errors on the same line. `@ts-ignore` is too broad.
## 5. Expecting `toReturn` to match TypeScript-narrowed types
**❌ Wrong**
```typescript
interface Svc {
get(): Promise
}
const mock = stub(['get'])
mock.setup.get.toReturn('value') // type error — get returns Promise, not string
```
**✅ Right**
Use `toResolveWith` for Promise-returning methods — it unwraps the resolved type for you:
```typescript
mock.setup.get.toResolveWith('value') // ✓ expects string (unwrapped from Promise)
```
**Why:** `toReturn` takes the raw method return type. For `Promise