ignore
Default. Continue silently. strictMode decides what happens next.
Scenarist provides comprehensive error handling with rich context, actionable hints, and configurable behaviors. All errors extend ScenaristError and include machine-readable codes for programmatic handling.
| Code | Description | When Thrown |
|---|---|---|
SCENARIO_NOT_FOUND | Attempted to switch to a scenario that hasn’t been registered | switchScenario() with unknown ID (returned as result.error, not thrown) |
DUPLICATE_SCENARIO | Attempted to register a scenario with an ID that already exists | registerScenario() with duplicate ID |
NO_MOCK_FOUND | No mock matched the incoming request | Unmatched request with errorBehaviors.onNoMockFound: 'throw' |
SEQUENCE_EXHAUSTED | Sequence with repeat: 'none' has no more responses and no fallback mock | Request to exhausted sequence with errorBehaviors.onSequenceExhausted: 'throw' |
MISSING_TEST_ID | No test ID could be resolved for the request | Request without a test ID header, with defaultTestId: '' and errorBehaviors.onMissingTestId: 'throw' |
VALIDATION_ERROR | Scenario definition failed schema validation | registerScenario() with invalid definition |
All Scenarist errors extend ScenaristError, which provides:
class ScenaristError extends Error { readonly code: string; // Machine-readable error code readonly context: ErrorContext; // Rich context information}
type ErrorContext = { readonly testId?: string; // Test that triggered the error readonly scenarioId?: string; // Scenario involved readonly requestInfo?: { // Request details method: string; url: string; }; readonly hint?: string; // Actionable guidance};switchScenario() returns a result instead of throwing. The error is typed as Error; at runtime it is a ScenaristError:
import { ScenaristError } from '@scenarist/core';
const result = scenarist.switchScenario('test-123', scenarioId);
if (!result.success && result.error instanceof ScenaristError) { console.log(result.error.code); // 'SCENARIO_NOT_FOUND' console.log(result.error.message); // "Scenario '...' not found..." console.log(result.error.context); // { // testId: 'test-123', // scenarioId: '...', // hint: 'Make sure to register the scenario before switching...' // }}The Express adapter type-checks scenario IDs against your scenarios object; the Next.js adapters accept any string. Over HTTP, POST /__scenario__ with an unknown scenario responds 400 with the error message.
Control how Scenarist handles request-time error conditions using errorBehaviors:
import { createScenarist, createConsoleLogger } from '@scenarist/express-adapter';
const scenarist = createScenarist({ enabled: process.env.NODE_ENV === 'test', scenarios, logger: createConsoleLogger({ level: 'warn' }), errorBehaviors: { onNoMockFound: 'warn', // Log a warning, then strictMode decides onSequenceExhausted: 'throw', // Respond 500 with SEQUENCE_EXHAUSTED },});ignore
Default. Continue silently. strictMode decides what happens next.
warn
Log a warning through the configured logger, then let strictMode decide. Without a logger, this behaves like ignore.
throw
Respond with HTTP 500 and a JSON body containing the error code and message. The request your application made receives that response.
A throw response looks like this:
{ "error": "Internal mock server error", "message": "No mock matched for GET https://api.example.com/users", "code": "NO_MOCK_FOUND"}All behaviors default to ignore, so an unmatched request behaves exactly as strictMode says:
const DEFAULT_ERROR_BEHAVIORS = { onNoMockFound: 'ignore', onSequenceExhausted: 'ignore', onMissingTestId: 'ignore',};Adapters fall back to defaultTestId ('default-test') when a request has no x-scenarist-test-id header, so by default a test ID is always resolved and onMissingTestId never fires. To make a missing header an error, disable the fallback:
const scenarist = createScenarist({ enabled: process.env.NODE_ENV === 'test', // Express; in Next.js use enabled: true scenarios, defaultTestId: '', errorBehaviors: { onMissingTestId: 'throw' },});When several instances are started in one process (for example, App Router and Pages Router adapters in the same Next.js app), each request is offered to every instance. A throw from one instance is used only if no other instance has a mock for the request.
Scenario definitions are validated at registration time using Zod schemas. Validation errors include the path to the invalid field:
try { manager.registerScenario({ id: 'test', name: '', // Empty name - validation error description: 'Test scenario', mocks: [ { method: 'GET', url: '', // Empty URL - validation error response: { status: 600 }, // Invalid status - validation error }, ], });} catch (error) { console.log(error.code); // 'VALIDATION_ERROR' console.log(error.message); // 'Invalid scenario definition for 'test': name: Too small: expected string to have >=1 characters, mocks.0.url: Too small: expected string to have >=1 characters, mocks.0.response.status: Too big: expected number to be <=599'}| Field | Rule |
|---|---|
id | Non-empty string |
name | Non-empty string |
url | Non-empty string or valid RegExp |
status | Integer between 100-599 |
sequence.responses | At least one response |
stateResponse.conditions[].when | At least one key |
afterResponse.setState | At least one key |
Validate your test setup by catching registration errors:
describe('API tests', () => { const scenarios = { default: createDefaultScenario(), error: createErrorScenario(), };
// Validate all scenarios at test suite startup beforeAll(() => { for (const [id, scenario] of Object.entries(scenarios)) { try { manager.registerScenario(scenario); } catch (error) { if (error instanceof ScenaristError) { throw new Error( `Invalid scenario '${id}': ${error.message}\nHint: ${error.context.hint}` ); } throw error; } } });});Use warn locally to surface issues without blocking, and throw in CI:
const scenarist = createScenarist({ enabled: true, scenarios, logger: createConsoleLogger({ level: 'warn' }), errorBehaviors: { onNoMockFound: process.env.CI ? 'throw' : 'warn', onSequenceExhausted: process.env.CI ? 'throw' : 'warn', },});Request-time errors (NO_MOCK_FOUND, SEQUENCE_EXHAUSTED, MISSING_TEST_ID) are not thrown into your application code. With throw, the external request your app made receives a 500 response whose body carries the code, so your app’s own error handling runs:
import { ErrorCodes } from '@scenarist/core';
// Errors thrown while handling a mocked request do not propagate to your code.// The request receives a 500 JSON response: { error, message, code }const response = await fetch('https://api.example.com/users');
if (response.status === 500) { const body = await response.json(); switch (body.code) { case ErrorCodes.NO_MOCK_FOUND: console.log('No mock matched:', body.message); break; case ErrorCodes.SEQUENCE_EXHAUSTED: console.log('Sequence exhausted for mock'); break; default: console.log('Scenarist error:', body.message); }}Switching errors are returned, not thrown: check result.success (see Example Error).
Registration errors (DUPLICATE_SCENARIO, VALIDATION_ERROR) are thrown as ScenaristError when scenarios are registered, which adapters do inside createScenarist(). See Validation Errors.
export const ErrorCodes = { SCENARIO_NOT_FOUND: 'SCENARIO_NOT_FOUND', DUPLICATE_SCENARIO: 'DUPLICATE_SCENARIO', NO_MOCK_FOUND: 'NO_MOCK_FOUND', SEQUENCE_EXHAUSTED: 'SEQUENCE_EXHAUSTED', MISSING_TEST_ID: 'MISSING_TEST_ID', VALIDATION_ERROR: 'VALIDATION_ERROR',} as const;type ErrorBehavior = 'throw' | 'warn' | 'ignore';
type ErrorBehaviors = { readonly onNoMockFound: ErrorBehavior; readonly onSequenceExhausted: ErrorBehavior; readonly onMissingTestId: ErrorBehavior;};class ScenaristError extends Error { constructor( message: string, options: { code: string; context: ErrorContext; } );
readonly code: string; readonly context: ErrorContext;}