throw
Default. Throw ScenaristError. Test fails immediately with clear error message.
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 |
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 | Request handling with strictMode: true or errorBehaviors.onNoMockFound: 'throw' |
SEQUENCE_EXHAUSTED | Sequence with repeat: 'none' has no more responses and no fallback mock | Request to exhausted sequence |
MISSING_TEST_ID | Request arrived without the x-scenarist-test-id header | Request without test context |
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};try { manager.switchScenario('test-123', 'nonexistent-scenario');} catch (error) { if (error instanceof ScenaristError) { console.log(error.code); // 'SCENARIO_NOT_FOUND' console.log(error.message); // 'Scenario 'nonexistent-scenario' not found...' console.log(error.context); // { // testId: 'test-123', // scenarioId: 'nonexistent-scenario', // hint: 'Make sure to register the scenario before switching...' // } }}Control how Scenarist handles specific error conditions using errorBehaviors:
import { createScenarist } from '@scenarist/express-adapter';
const scenarist = createScenarist({ enabled: true, scenarios, errorBehaviors: { onNoMockFound: 'warn', // Log warning, continue onSequenceExhausted: 'throw', // Throw error (fail test) onMissingTestId: 'warn', // Log warning, use default scenario },});throw
Default. Throw ScenaristError. Test fails immediately with clear error message.
warn
Log warning via Logger port, then continue. Good for development debugging.
ignore
Silently continue without logging. Use sparingly.
All behaviors default to throw for strict-by-default testing:
const DEFAULT_ERROR_BEHAVIORS = { onNoMockFound: 'throw', onSequenceExhausted: 'throw', onMissingTestId: 'throw',};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: String must contain at least 1 character(s), mocks.0.url: String must contain at least 1 character(s), mocks.0.response.status: Number must be at most 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; } } });});For development environments, use warn to surface issues without blocking:
const scenarist = createScenarist({ enabled: true, scenarios, logger: createConsoleLogger({ level: 'warn' }), errorBehaviors: { onNoMockFound: process.env.CI ? 'throw' : 'warn', onMissingTestId: process.env.CI ? 'throw' : 'warn', },});Use error codes to handle specific error types:
import { ScenaristError, ErrorCodes } from '@scenarist/core';
try { await makeRequest('/api/users');} catch (error) { if (error instanceof ScenaristError) { switch (error.code) { case ErrorCodes.NO_MOCK_FOUND: console.log('No mock matched:', error.context.requestInfo?.url); break; case ErrorCodes.SEQUENCE_EXHAUSTED: console.log('Sequence exhausted for mock'); break; default: console.log('Scenarist error:', error.message); } }}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;}