or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

app-wrappers.mdindex.mdmock-apis.mdmsw-integration.mdtest-api-provider.mdtesting-utilities.md
tile.json

msw-integration.mddocs/

MSW Integration

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.

Capabilities

Register MSW Test Hooks

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');
  });
});

Advanced MSW Setup

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');
  });
});

Per-Test Handler Overrides

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');
  });
});

Request Inspection and Validation

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)
    });
  });
});

Legacy Support (Deprecated)

setupRequestMockHandlers

/**
 * @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.

Best Practices

Organizing MSW Handlers

// 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 };

Environment-Specific Configuration

// 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);

Types

/**
 * 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;
}