msw-director
v0.1.0
Published
A flexible MSW wrapper for testing and development with runtime control and scenarios
Maintainers
Readme
msw-director
Stop hardcoding mocks. Control your API state dynamically in the browser and tests.
A flexible wrapper for Mock Service Worker (MSW) designed to solve the "Mock Management Hell".
The Problem
Standard mocking is great until you need to test edge cases.
- How do I test a 500 error without changing the code?
- How do I switch the user to "Suspended" state for a demo?
- How do I run parallel tests with different mock states?
The Solution
msw-director separates the Mock Definition from the Mock State.
- Define all possible responses (Success, Error, Loading) once.
- Switch between them at runtime using the Console or Test Controller.
Features
- 🛠 Fluent Handler Builder: Define variants cleanly (
.add(200, ...).add(500, ...)). - 🎮 Runtime Control: Switch states instantly via
window.mswControllerin the browser. - 🧪 Test Isolation: Thread-safe controllers for parallel Jest/Vitest tests.
- 📦 Scenarios: Group complex app states (e.g., "Maintenance Mode") into presets.
- 🍪 SSR Support: Persist mock state via cookies for Server-Side Rendering.
When to use (and when NOT to)
| ✅ Use this if... | ❌ Do NOT use if... | | :--- | :--- | | You need to manually test edge cases (QA/Demo). | You need a full-blown Backend-for-Frontend (BFF). | | You have flaky tests due to shared mock state. | You are mocking WebSockets (not supported yet). | | You want to simulate network delays dynamically. | You want to use this in Production code. |
Installation
First, ensure msw is installed.
yarn add -D msw msw-director
# or
npm install --save-dev msw msw-directorQuick Start
1. Define Your Handlers
// src/mocks/handlers.ts
import { HandlerBuilder, corsHandler } from 'msw-director';
import { HttpResponse } from 'msw';
export const handlers = [
corsHandler,
HandlerBuilder.get('/api/user', 'getUser')
.add(200, () => HttpResponse.json({ name: 'John Doe' }))
.add(404, () => new HttpResponse(null, { status: 404 }))
.add('suspended', () => HttpResponse.json({ status: 'suspended' }))
.withDelay(300),
];2. Initialize in Browser
// src/main.tsx
import { initBrowserMocks } from 'msw-director';
import { handlers } from './mocks/handlers';
if (process.env.NODE_ENV === 'development') {
initBrowserMocks({ handlers });
}3. Control it!
Open your browser console:
// Switch to 404 error
window.mswController.setHandler('getUser', 404);
// Switch to custom state
window.mswController.setHandler('getUser', 'suspended');
// Reset to default
window.mswController.clearHandler('getUser');Advanced Usage: Scenarios
Scenarios allow you to activate a group of handlers at once.
Defining Scenarios
// src/mocks/scenarios.ts
import type { TScenarios } from 'msw-director';
export const scenarios: TScenarios = {
'auth-error': {
type: 'auth', // Mutually exclusive group
handlers: {
getUser: 401,
getSettings: 401,
},
},
'maintenance-mode': {
type: 'global',
handlers: {
getUser: 503,
getSettings: 503,
},
},
};Activating Scenarios
You can activate scenarios programmatically (e.g., in tests) or via the console.
In Browser Console:
window.mswController.setScenario('auth-error');In Tests:
it('should show maintenance screen', async () => {
controller.setScenario('maintenance-mode');
render(<App />);
await screen.findByText('Under Maintenance');
});Global Configuration
You can configure global defaults during initialization.
initBrowserMocks({
handlers,
scenarios,
defaultDelay: 500, // Global delay for all handlers (unless overridden)
storeApi: getCookieStorageApi(), // Use cookies instead of sessionStorage
});Usage in Tests (Jest/Vitest)
We provide a factory pattern to ensure Test Isolation.
// jest.setup.ts
import { initNodeMocks, RequestSpy } from 'msw-director';
import { handlers, scenarios } from './src/mocks';
export const requestSpy = new RequestSpy();
// Returns a unique controller for this test suite/file
export const { server, controller } = initNodeMocks({
handlers,
scenarios, // Don't forget to pass scenarios here!
loggers: [requestSpy.log],
});
beforeAll(() => server.listen());
afterEach(() => {
server.resetHandlers();
requestSpy.clear();
controller.clearAllHandlers(); // Reset state
controller.clearAllScenarios(); // Reset active scenarios
});
afterAll(() => server.close());Writing Tests
import { controller, requestSpy } from './jest.setup';
describe('UserProfile', () => {
it('should display an error on 500', async () => {
// Set the handler to return a 500 error
controller.setHandler('getUser', 500);
render(<UserProfile />);
await screen.findByText('Something went wrong');
});
it('should handle auth scenario', async () => {
// Activate a complex scenario
controller.setScenario('auth-error');
render(<UserProfile />);
await screen.findByText('Please login');
});
});Common Pitfalls (Anti-Patterns)
❌ Don't use window in Tests
Bad: Trying to access window.mswController in Jest/Node environment.
Good: Use the controller instance returned by initNodeMocks.
❌ Don't put logic in Resolvers
Bad: Writing complex if/else business logic inside .add(...).
Good: Mocks should be dumb. If you need logic, create a separate handler variant.
❌ Don't forget to initialize
Bad: Using HandlerBuilder without calling initBrowserMocks or initNodeMocks.
Result: The handler will warn in the console and fallback to passthrough.
API Reference
HandlerBuilder
.get(path, id): Create a GET handler..add(key, resolver): Add a response variant..withDelay(ms): Set default delay..skipDefaultError(): Disable auto-4xx generation.
MockController
.setHandler(id, key): Override a specific handler..setScenario(name): Activate a scenario..resolve(ctx): Internal method for state resolution.
Legal & Security
Data Privacy
This library runs entirely on the client-side (Browser or Node.js process). It does not collect telemetry or send data to external servers.
Trademarks
- MSW is a trademark of its respective owners. This project is not affiliated with MSW.
- React, Jest, and Vitest are trademarks of their respective owners.
Disclaimer
The software is provided "AS IS", without warranty of any kind, express or implied. The authors are not liable for any claim, damages, or other liability arising from the use of this software.
License
MIT
