Getting Started

April 4, 2026 · View on GitHub

Installation

bun install @schmock/core

Optional packages:

bun install @schmock/react      # React Provider + hook
bun install @schmock/vue        # Vue 3 Plugin + composable
bun install @schmock/express    # Express middleware adapter
bun install @schmock/angular    # Angular HTTP interceptor
bun install @schmock/openapi    # Auto-mock from OpenAPI specs
bun install @schmock/faker      # Schema-based data generation
bun install @schmock/validation # Request/response validation
bun install @schmock/query      # Pagination, sorting, filtering
bun install @schmock/cli        # Standalone CLI server

Core Concepts

1. Create a mock instance

import { schmock } from '@schmock/core'

const mock = schmock()

With options:

const mock = schmock({
  namespace: '/api/v1',           // prefix all routes
  state: { users: [], counter: 0 }, // shared mutable state
  delay: 100,                     // simulate latency (ms)
  debug: true,                    // log request lifecycle
})

2. Define routes

Routes are defined by calling the instance directly:

mock('GET /health', { status: 'ok' })

The first argument is a RouteKey in the format METHOD /path. The second is a generator — it can be:

Static data — returned as-is:

mock('GET /config', { version: '2.0', env: 'staging' })

Generator function — called on each request:

mock('GET /users/:id', ({ params, state }) => {
  const user = state.users.find(u => u.id === Number(params.id))
  return user || [404, { error: 'Not found' }]
})

Tuple responses — control status codes and headers:

mock('POST /users', ({ body, state }) => {
  const user = { id: ++state.counter, ...body }
  state.users.push(user)
  return [201, user, { 'x-created-id': String(user.id) }]
})

3. Handle requests

const res = await mock.handle('GET', '/health')
// → { status: 200, body: { status: 'ok' }, headers: { 'content-type': 'application/json' } }

const res = await mock.handle('POST', '/users', {
  body: { name: 'Alice' },
  headers: { authorization: 'Bearer token' },
  query: { notify: 'true' },
})

handle() never throws — errors become response objects with appropriate status codes.

4. Use plugins

Plugins add behavior through a linear pipeline:

import { validationPlugin } from '@schmock/validation'

mock('POST /users', handler)
  .pipe(validationPlugin({
    request: {
      body: {
        type: 'object',
        required: ['name', 'email'],
        properties: {
          name: { type: 'string', minLength: 1 },
          email: { type: 'string', format: 'email' },
        },
      },
    },
  }))

Plugins execute in .pipe() order. The first plugin to set a response becomes the generator; later plugins can transform it.

State Management

State is shared across all routes and persists between requests:

const mock = schmock({ state: { users: [], nextId: 1 } })

mock('POST /users', ({ body, state }) => {
  const user = { id: state.nextId++, ...body }
  state.users.push(user)
  return [201, user]
})

mock('GET /users', ({ state }) => state.users)

mock('DELETE /users/:id', ({ params, state }) => {
  const idx = state.users.findIndex(u => u.id === Number(params.id))
  if (idx === -1) return [404, { error: 'Not found' }]
  state.users.splice(idx, 1)
  return [204, null]
})

Reset state without clearing routes:

mock.resetState()   // reset state to initial config values
mock.resetHistory() // clear request history only
mock.reset()        // full reset: routes, state, history, plugins, stop server

Request Spying

Every request is recorded for assertions:

await mock.handle('POST', '/users', { body: { name: 'Alice' } })
await mock.handle('POST', '/users', { body: { name: 'Bob' } })

mock.called()                   // true (any request was made)
mock.called('POST', '/users')   // true
mock.callCount('POST', '/users') // 2

const last = mock.lastRequest('POST', '/users')
// { method: 'POST', path: '/users', body: { name: 'Bob' }, timestamp: ..., response: { status: 201, body: ... } }

const all = mock.history('POST', '/users')
// Array of all POST /users request records

Lifecycle Events

Subscribe to request lifecycle events:

mock.on('request:start', ({ method, path }) => {
  console.log(`→ ${method} ${path}`)
})

mock.on('request:end', ({ method, path, status, duration }) => {
  console.log(`← ${method} ${path} ${status} (${duration}ms)`)
})

mock.on('request:notfound', ({ method, path }) => {
  console.warn(`No route for ${method} ${path}`)
})

Route Introspection

const routes = mock.getRoutes()
// [{ method: 'GET', path: '/users', hasParams: false },
//  { method: 'GET', path: '/users/:id', hasParams: true }]

Standalone HTTP Server

Run any mock as a real HTTP server:

const info = await mock.listen(3000)
// Listening on http://127.0.0.1:3000

// Use port 0 for a random available port (great for tests)
const info = await mock.listen(0)
console.log(`Running on port ${info.port}`)

// Stop the server
mock.close()

Next Steps