Skip to content

Error Handling

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.

CodeDescriptionWhen Thrown
SCENARIO_NOT_FOUNDAttempted to switch to a scenario that hasn’t been registeredswitchScenario() with unknown ID
DUPLICATE_SCENARIOAttempted to register a scenario with an ID that already existsregisterScenario() with duplicate ID
NO_MOCK_FOUNDNo mock matched the incoming requestRequest handling with strictMode: true or errorBehaviors.onNoMockFound: 'throw'
SEQUENCE_EXHAUSTEDSequence with repeat: 'none' has no more responses and no fallback mockRequest to exhausted sequence
MISSING_TEST_IDRequest arrived without the x-scenarist-test-id headerRequest without test context
VALIDATION_ERRORScenario definition failed schema validationregisterScenario() 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'
}
FieldRule
idNon-empty string
nameNon-empty string
urlNon-empty string or valid RegExp
statusInteger between 100-599
sequence.responsesAt least one response
stateResponse.conditions[].whenAt least one key
afterResponse.setStateAt 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;
}