Skip to content

Scenarist vs Nock

Nock is a popular HTTP mocking library for Node.js. Since v14, Nock uses @mswjs/interceptors—the same interception engine as MSW—making it robust and modern. Scenarist takes a different approach—declarative scenario definitions with built-in parallel test isolation.

What Scenarist Offers

Before diving into comparisons, here's what Scenarist brings to the table:

  • Simple Architecture — Just an HTTP header (x-scenarist-test-id). No Docker, no separate processes, no complex network configuration
  • Test ID Isolation — Run hundreds of parallel tests with different scenarios against one server. Each test's header routes to its own scenario
  • Runtime Switching — Change scenarios mid-test without restarts (retry flows, error recovery)
  • First-Class Playwright — Dedicated fixtures with type-safe scenarios and automatic test ID handling
  • Response Sequences — Built-in polling, retry flows, state machines
  • Stateful Mocks — Capture request values, inject into responses. State is isolated per test ID, so parallel tests never conflict
  • Advanced Matching — Body, headers, query params, regex with specificity-based selection
  • Framework Adapters — Not thin wrappers—they solve real problems. For example, the Next.js adapter includes built-in singleton protection for the module duplication issue that breaks MSW
  • Developer Tools (Roadmap) — Planned browser-based plugin for switching scenarios during development and debugging—making scenario exploration instant and visual
AspectScenaristNock
ApproachDeclarative scenariosImperative per-test setup
Test isolationPer-test via test IDManual scope management
Interception levelNetwork (MSW)@mswjs/interceptors (v14+)
Runtime switchingYes (API call)Manual (cleanAll + reconfigure)
Browser supportVia MSWNode.js only
SetupFramework adaptersStandalone library
TypeScriptNative (scenarios are TS)Built-in definitions
RecordingNot supportednockBack fixture recording
RSC scenario testing✓ (server-side interception)✗ (process isolation issue)
// Declarative - describe what, not how
const scenarios = {
'user-premium': {
mocks: [{
url: 'https://api.stripe.com/v1/customers/cus_123',
response: {
status: 200,
body: { id: 'cus_123', subscriptions: { data: [{ status: 'active' }] } }
}
}]
},
'user-free': {
mocks: [{
url: 'https://api.stripe.com/v1/customers/cus_123',
response: {
status: 200,
body: { id: 'cus_123', subscriptions: { data: [] } }
}
}]
}
} as const satisfies ScenaristScenarios;
// Tests select scenarios by name
test('premium features visible', async ({ switchScenario }) => {
await switchScenario(page, 'user-premium');
});

Trade-off: Scenarist’s declarative approach makes scenarios reusable and inspectable—you can see all scenarios in one place. Nock’s imperative approach is more flexible for one-off mocks but can lead to duplication.

// Built-in test isolation via test ID
test.describe.parallel('Payment flows', () => {
test('success flow', async ({ page, switchScenario }) => {
// x-test-id header routes to this scenario
await switchScenario(page, 'payment-success');
// Other tests can run simultaneously with different scenarios
});
test('failure flow', async ({ page, switchScenario }) => {
await switchScenario(page, 'payment-declined');
// Same server, same time, isolated by test ID
});
});

Trade-off: Scenarist was designed specifically for parallel test isolation. Nock can work with parallel tests but requires careful scope management to avoid mock leakage between tests.

// Declarative matching patterns
{
method: 'POST',
url: 'https://api.stripe.com/v1/charges',
match: {
body: {
amount: 5000,
currency: 'usd'
},
headers: {
'idempotency-key': /^[a-f0-9-]+$/
}
},
response: {
status: 200,
body: { id: 'ch_123' }
}
}

Trade-off: Both offer rich matching capabilities. Nock allows function matchers for maximum flexibility. Scenarist’s declarative patterns are more restrictive but enable inspection and composition.

// Scenarios persist - switch between them
test('multi-step flow', async ({ switchScenario }) => {
// Start with error
await switchScenario(page, 'payment-timeout');
await page.click('#submit');
// Switch to success for retry
await switchScenario(page, 'payment-success');
await page.click('#retry');
// No cleanup needed - test ID isolation
});

Trade-off: Nock’s one-time consumption is explicit about expected call counts. Scenarist’s persistent scenarios are simpler for flows where the same endpoint is called multiple times.

Server Component Testing: The Process Isolation Problem

Section titled “Server Component Testing: The Process Isolation Problem”

Nock intercepts requests in the current Node.js process. In E2E testing with Playwright, your test runs in one process while the Next.js server runs in a separate process—Nock in the test process can’t intercept requests from the server process.

// ❌ E2E Test - FAILS
// Test process
import nock from 'nock';
nock('https://api.stripe.com')
.get('/v1/products')
.reply(200, { products: [] });
// Playwright launches browser, browser requests page from Next.js server
// Next.js server (SEPARATE PROCESS) fetches from Stripe
// Nock never sees it - the request goes to real Stripe
await page.goto('/products'); // Fails or uses real API

Key insight: Scenarist runs inside your application server (via framework adapters), so it intercepts requests where they originate. Nock runs in your test process and can only intercept requests made from that process.

Scenario-based testing with Server Components

Scenarist’s server-side interception works across process boundaries. Nock can’t intercept requests from a separate Next.js server process.

Parallel test isolation

Built-in test ID system enables hundreds of tests with different scenarios running simultaneously.

Scenario libraries

Define scenarios once, reuse everywhere. Changes in one place update all tests.

Runtime switching

Change scenarios mid-test without cleanup/setup. Test retry flows, state machines.

Integration tests (same process)

When test and server run in the same process, Nock works great. E2E with separate processes—use Scenarist.

Fixture recording (nockBack)

Record real HTTP interactions to JSON files, replay in tests. Great for contract snapshots.

Call counting

Built-in assertions on call counts. Verify exactly N requests were made.

Existing investment

Team already uses Nock. Migration cost outweighs benefits for integration tests.

// Nock - inline definitions
beforeEach(() => {
nock('https://api.stripe.com')
.get('/v1/customers/cus_123')
.reply(200, { id: 'cus_123', name: 'Test Customer' });
nock('https://api.sendgrid.com')
.post('/v3/mail/send')
.reply(202);
});
// Scenarist - centralized scenarios
const scenarios = {
default: {
mocks: [
{
url: 'https://api.stripe.com/v1/customers/cus_123',
response: { status: 200, body: { id: 'cus_123', name: 'Test Customer' } }
},
{
url: 'https://api.sendgrid.com/v3/mail/send',
method: 'POST',
response: { status: 202 }
}
]
}
} as const satisfies ScenaristScenarios;

Migration benefits:

  • Scenarios become visible in one place
  • Test isolation improves automatically
  • Runtime switching becomes possible

Migration costs:

  • Learn declarative patterns instead of fluent API
  • Convert inline mocks to scenarios
  • Set up framework adapter
FactorScenaristNock
RSC scenario testing✓ Works (server-side)✗ Process isolation issue
Parallel isolation✓ Built-in (test ID)Manual
Scenario reuse✓ DeclarativeManual extraction
Runtime switching✓ Single API callReconfigure handlers
TypeScript✓ Native✓ Built-in definitions
RecordingNot supported✓ nockBack
Function matchersDeclarative patterns✓ Full flexibility
Call count assertionsNot built-in✓ Built-in
Integration tests✓ Works✓ Works

Bottom line: Choose Scenarist for scenario-based testing with Server Components (where Nock can’t reach across processes), parallel test isolation, and runtime switching. Choose Nock for integration tests in the same process, when you need fixture recording (nockBack), or when you need function matchers and call counting.