Mock Service Worker integration for HTTP request mocking in tests. This utility provides seamless setup and teardown of MSW (Mock Service Worker) for intercepting and mocking HTTP requests during test execution.
Sets up Mock Service Worker hooks for request mocking with automatic lifecycle management.
/**
* Sets up MSW (Mock Service Worker) hooks for request mocking in tests
* Registers beforeAll, afterAll, and afterEach hooks for proper MSW lifecycle
* @param worker - MSW worker instance with required methods
*/
function registerMswTestHooks(worker: {
listen: (t: any) => void;
close: () => void;
resetHandlers: () => void;
}): void;Usage Examples:
import { setupServer } from 'msw/node';
import { rest } from 'msw';
import { registerMswTestHooks } from '@backstage/test-utils';
// Create MSW server with request handlers
const server = setupServer(
rest.get('/api/users', (req, res, ctx) => {
return res(
ctx.json([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' }
])
);
}),
rest.post('/api/users', (req, res, ctx) => {
return res(ctx.status(201), ctx.json({ id: 3, name: 'New User' }));
})
);
// Register MSW hooks for all tests in the file
registerMswTestHooks(server);
describe('API Integration Tests', () => {
test('fetches users successfully', async () => {
const response = await fetch('/api/users');
const users = await response.json();
expect(users).toHaveLength(2);
expect(users[0].name).toBe('John Doe');
});
test('creates user successfully', async () => {
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'New User' })
});
expect(response.status).toBe(201);
const user = await response.json();
expect(user.name).toBe('New User');
});
});import { setupServer } from 'msw/node';
import { rest } from 'msw';
import { registerMswTestHooks } from '@backstage/test-utils';
// Setup with error handlers
const server = setupServer(
// Success responses
rest.get('/api/catalog/entities', (req, res, ctx) => {
return res(
ctx.json([
{ kind: 'Component', metadata: { name: 'my-service' } },
{ kind: 'API', metadata: { name: 'my-api' } }
])
);
}),
// Error responses
rest.get('/api/catalog/entities/:name', (req, res, ctx) => {
const { name } = req.params;
if (name === 'not-found') {
return res(ctx.status(404), ctx.json({ error: 'Entity not found' }));
}
return res(ctx.json({ kind: 'Component', metadata: { name } }));
}),
// Network error simulation
rest.get('/api/unreliable', (req, res, ctx) => {
return res.networkError('Connection failed');
})
);
registerMswTestHooks(server);
describe('Error Handling', () => {
test('handles 404 responses', async () => {
const response = await fetch('/api/catalog/entities/not-found');
expect(response.status).toBe(404);
const error = await response.json();
expect(error.error).toBe('Entity not found');
});
test('handles network errors', async () => {
await expect(fetch('/api/unreliable')).rejects.toThrow('Connection failed');
});
});import { rest } from 'msw';
import { server } from './test-setup'; // Assuming server is exported
describe('Dynamic Handler Tests', () => {
test('overrides handler for specific test', async () => {
// Override the default handler for this test only
server.use(
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.json([])); // Return empty array
})
);
const response = await fetch('/api/users');
const users = await response.json();
expect(users).toHaveLength(0);
});
test('uses default handler', async () => {
// This test uses the original handler
const response = await fetch('/api/users');
const users = await response.json();
expect(users).toHaveLength(2); // Original mock data
});
test('adds new handler for test', async () => {
// Add a new handler that wasn't in the original setup
server.use(
rest.get('/api/new-endpoint', (req, res, ctx) => {
return res(ctx.json({ message: 'New endpoint' }));
})
);
const response = await fetch('/api/new-endpoint');
const data = await response.json();
expect(data.message).toBe('New endpoint');
});
});import { rest } from 'msw';
import { registerMswTestHooks } from '@backstage/test-utils';
const requestSpy = jest.fn();
const server = setupServer(
rest.post('/api/analytics', (req, res, ctx) => {
// Capture request details for validation
requestSpy({
method: req.method,
url: req.url.toString(),
headers: Object.fromEntries(req.headers.entries()),
body: req.body
});
return res(ctx.status(200));
})
);
registerMswTestHooks(server);
describe('Request Validation', () => {
beforeEach(() => {
requestSpy.mockClear();
});
test('sends correct analytics payload', async () => {
const analyticsData = {
event: 'page_view',
properties: { page: '/catalog' }
};
await fetch('/api/analytics', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(analyticsData)
});
expect(requestSpy).toHaveBeenCalledWith({
method: 'POST',
url: expect.stringContaining('/api/analytics'),
headers: expect.objectContaining({
'content-type': 'application/json'
}),
body: expect.any(Object)
});
});
});/**
* @deprecated Use registerMswTestHooks instead
* Legacy function for MSW setup
* @param worker - MSW worker instance
*/
function setupRequestMockHandlers(worker: {
listen: (t: any) => void;
close: () => void;
resetHandlers: () => void;
}): void;This function is deprecated and simply calls registerMswTestHooks internally. Use registerMswTestHooks directly for new code.
// handlers/catalog.ts
import { rest } from 'msw';
export const catalogHandlers = [
rest.get('/api/catalog/entities', (req, res, ctx) => {
return res(ctx.json(mockEntities));
}),
rest.get('/api/catalog/entities/:kind/:namespace/:name', (req, res, ctx) => {
const { kind, namespace, name } = req.params;
const entity = findMockEntity(kind, namespace, name);
if (!entity) {
return res(ctx.status(404));
}
return res(ctx.json(entity));
})
];
// handlers/index.ts
import { catalogHandlers } from './catalog';
import { authHandlers } from './auth';
import { scaffolderHandlers } from './scaffolder';
export const handlers = [
...catalogHandlers,
...authHandlers,
...scaffolderHandlers
];
// test-setup.ts
import { setupServer } from 'msw/node';
import { registerMswTestHooks } from '@backstage/test-utils';
import { handlers } from './handlers';
const server = setupServer(...handlers);
registerMswTestHooks(server);
export { server };// Different configurations for different test environments
const baseHandlers = [
rest.get('/api/health', (req, res, ctx) => {
return res(ctx.json({ status: 'ok' }));
})
];
const developmentHandlers = [
...baseHandlers,
rest.get('/api/debug', (req, res, ctx) => {
return res(ctx.json({ debug: true }));
})
];
const productionHandlers = [
...baseHandlers,
rest.get('/api/debug', (req, res, ctx) => {
return res(ctx.status(404));
})
];
const server = setupServer(
...(process.env.NODE_ENV === 'development'
? developmentHandlers
: productionHandlers)
);
registerMswTestHooks(server);/**
* MSW worker interface for test hook registration
*/
interface MswWorker {
/** Start intercepting requests with options */
listen: (options?: { onUnhandledRequest?: 'bypass' | 'warn' | 'error' }) => void;
/** Stop intercepting requests and clean up */
close: () => void;
/** Reset all handlers to their initial state */
resetHandlers: () => void;
}
/**
* MSW request handler types
*/
type RequestHandler = import('msw').RequestHandler;
type RestHandler = import('msw').RestHandler;
/**
* MSW response utilities
*/
interface ResponseUtils {
status: (code: number) => ResponseTransformer;
json: (body: any) => ResponseTransformer;
text: (body: string) => ResponseTransformer;
xml: (body: string) => ResponseTransformer;
delay: (duration?: number | 'infinite') => ResponseTransformer;
networkError: (message: string) => ResponseTransformer;
}