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

testing-utilities.mddocs/

Testing Utilities

Enhanced testing capabilities including log collection, async rendering, and breakpoint mocking for comprehensive test coverage. These utilities extend React Testing Library with Backstage-specific functionality.

Capabilities

Log Collection

Captures console output during test execution for verification and debugging.

/**
 * Captures console logs during async callback execution
 * @param callback - Async function to execute while collecting logs
 * @returns Promise resolving to collected logs by type
 */
function withLogCollector(callback: () => Promise<void>): Promise<CollectedLogs<LogFuncs>>;

/**
 * Captures console logs during synchronous callback execution
 * @param callback - Synchronous function to execute while collecting logs
 * @returns Collected logs by type
 */
function withLogCollector(callback: () => void): CollectedLogs<LogFuncs>;

/**
 * Captures specific log types during async callback execution
 * @param logsToCollect - Array of log types to capture
 * @param callback - Async function to execute while collecting logs
 * @returns Promise resolving to collected logs for specified types
 */
function withLogCollector<T extends readonly string[]>(
  logsToCollect: T, 
  callback: () => Promise<void>
): Promise<CollectedLogs<T>>;

/**
 * Captures specific log types during synchronous callback execution
 * @param logsToCollect - Array of log types to capture
 * @param callback - Synchronous function to execute while collecting logs
 * @returns Collected logs for specified types
 */
function withLogCollector<T extends readonly string[]>(
  logsToCollect: T,
  callback: () => void
): CollectedLogs<T>;

type LogFuncs = 'log' | 'warn' | 'error';
type CollectedLogs<T extends readonly string[]> = { [K in T[number]]: string[] };

Usage Examples:

import { withLogCollector } from "@backstage/test-utils";

// Collect all console output types
const logs = await withLogCollector(async () => {
  console.log('Info message');
  console.warn('Warning message');
  console.error('Error message');
  
  // Execute component that produces logs
  await someAsyncOperation();
});

expect(logs.log).toContain('Info message');
expect(logs.warn).toContain('Warning message');
expect(logs.error).toContain('Error message');

// Collect only specific log types
const errorLogs = await withLogCollector(['error'], async () => {
  console.log('This will not be captured');
  console.error('This will be captured');
  
  render(<ComponentThatMightError />);
});

expect(errorLogs.error).toHaveLength(1);
expect(errorLogs.error[0]).toContain('This will be captured');

// Synchronous log collection
const syncLogs = withLogCollector(() => {
  console.warn('Synchronous warning');
  someFunction();
});

expect(syncLogs.warn).toContain('Synchronous warning');

Async Rendering with Effects

Renders React components with proper handling of async effects using React's act utility.

/**
 * Renders components with async effects wrapped in act() utility
 * @param nodes - React element to render
 * @param options - Render options including legacy root support
 * @returns Promise resolving to React Testing Library render result
 */
function renderWithEffects(
  nodes: ReactElement,
  options?: RenderOptions & LegacyRootOption
): Promise<RenderResult>;

interface LegacyRootOption {
  /** Use React 17 legacy root API instead of React 18 createRoot */
  legacyRoot?: boolean;
}

interface RenderOptions {
  container?: HTMLElement;
  baseElement?: HTMLElement;
  wrapper?: React.ComponentType<{ children: React.ReactNode }>;
}

Usage Examples:

import { renderWithEffects } from "@backstage/test-utils";

// Render component with async effects
const { getByText, findByText } = await renderWithEffects(
  <ComponentWithAsyncEffects />
);

// Wait for async content to appear
expect(await findByText('Loaded data')).toBeInTheDocument();

// With wrapper component
const { container } = await renderWithEffects(
  <MyComponent />,
  {
    wrapper: ({ children }) => (
      <ThemeProvider theme={mockTheme}>
        {children}
      </ThemeProvider>
    )
  }
);

// With legacy root for React 17 compatibility
await renderWithEffects(<LegacyComponent />, { legacyRoot: true });

Mock Breakpoint (Deprecated)

Mocks window.matchMedia for Material-UI's useMediaQuery hook testing.

/**
 * @deprecated Use @backstage/core-components/testUtils instead
 * Mocks window.matchMedia for Material UI useMediaQuery testing
 * @param options - Breakpoint configuration
 * @param options.matches - Whether the media query should match
 */
function mockBreakpoint(options: { matches: boolean }): void;

Usage Examples:

import { mockBreakpoint } from "@backstage/test-utils";

describe('ResponsiveComponent', () => {
  test('shows mobile layout on small screens', () => {
    mockBreakpoint({ matches: true }); // Simulate small screen
    
    render(<ResponsiveComponent />);
    expect(screen.getByTestId('mobile-layout')).toBeInTheDocument();
  });

  test('shows desktop layout on large screens', () => {
    mockBreakpoint({ matches: false }); // Simulate large screen
    
    render(<ResponsiveComponent />);
    expect(screen.getByTestId('desktop-layout')).toBeInTheDocument();
  });

  afterEach(() => {
    // Clean up mock
    jest.restoreAllMocks();
  });
});

Advanced Usage Patterns

Comprehensive Log Testing

import { withLogCollector, renderInTestApp } from "@backstage/test-utils";

test('component logs expected messages during lifecycle', async () => {
  const logs = await withLogCollector(['log', 'warn', 'error'], async () => {
    const { rerender, unmount } = await renderInTestApp(<MyComponent />);
    
    // Trigger state changes that might produce logs
    fireEvent.click(screen.getByRole('button'));
    
    // Update props
    rerender(<MyComponent updated={true} />);
    
    // Unmount component
    unmount();
  });

  // Verify expected log messages
  expect(logs.log).toContain('Component initialized');
  expect(logs.warn).toContain('Deprecated prop used');
  expect(logs.error).toHaveLength(0); // No errors expected
});

Error Boundary Testing

import { withLogCollector, renderWithEffects } from "@backstage/test-utils";

function ErrorBoundary({ children }: { children: React.ReactNode }) {
  // Error boundary implementation
}

test('error boundary catches and logs errors', async () => {
  const logs = await withLogCollector(['error'], async () => {
    await renderWithEffects(
      <ErrorBoundary>
        <ComponentThatThrows />
      </ErrorBoundary>
    );
  });

  expect(logs.error).toContain('Component error caught');
});

Performance Testing with Logs

import { withLogCollector } from "@backstage/test-utils";

test('component performance is within acceptable range', async () => {
  const logs = await withLogCollector(['log'], async () => {
    performance.mark('component-start');
    
    await renderInTestApp(<PerformanceComponent />);
    
    performance.mark('component-end');
    performance.measure('component-render', 'component-start', 'component-end');
    
    const measure = performance.getEntriesByName('component-render')[0];
    console.log(`Render time: ${measure.duration}ms`);
  });

  const renderTimeLog = logs.log.find(log => log.includes('Render time:'));
  expect(renderTimeLog).toBeDefined();
  
  const duration = parseFloat(renderTimeLog!.match(/(\d+\.?\d*)ms/)?.[1] || '0');
  expect(duration).toBeLessThan(100); // Less than 100ms
});

Types

type ReactElement = React.ReactElement;

interface RenderResult {
  container: HTMLElement;
  baseElement: HTMLElement;
  debug: (baseElement?: HTMLElement | DocumentFragment) => void;
  rerender: (ui: React.ReactElement) => void;
  unmount: () => boolean;
  asFragment: () => DocumentFragment;
  
  // React Testing Library query methods
  getByLabelText: (text: string | RegExp) => HTMLElement;
  getByPlaceholderText: (text: string | RegExp) => HTMLElement;
  getByText: (text: string | RegExp) => HTMLElement;
  getByDisplayValue: (text: string | RegExp) => HTMLElement;
  getByAltText: (text: string | RegExp) => HTMLElement;
  getByTitle: (text: string | RegExp) => HTMLElement;
  getByRole: (role: string, options?: any) => HTMLElement;
  getByTestId: (testId: string) => HTMLElement;
  
  // Async query methods
  findByLabelText: (text: string | RegExp) => Promise<HTMLElement>;
  findByPlaceholderText: (text: string | RegExp) => Promise<HTMLElement>;
  findByText: (text: string | RegExp) => Promise<HTMLElement>;
  findByDisplayValue: (text: string | RegExp) => Promise<HTMLElement>;
  findByAltText: (text: string | RegExp) => Promise<HTMLElement>;
  findByTitle: (text: string | RegExp) => Promise<HTMLElement>;
  findByRole: (role: string, options?: any) => Promise<HTMLElement>;
  findByTestId: (testId: string) => Promise<HTMLElement>;
  
  // Multiple element queries
  getAllByLabelText: (text: string | RegExp) => HTMLElement[];
  getAllByPlaceholderText: (text: string | RegExp) => HTMLElement[];
  getAllByText: (text: string | RegExp) => HTMLElement[];
  getAllByDisplayValue: (text: string | RegExp) => HTMLElement[];
  getAllByAltText: (text: string | RegExp) => HTMLElement[];
  getAllByTitle: (text: string | RegExp) => HTMLElement[];
  getAllByRole: (role: string, options?: any) => HTMLElement[];
  getAllByTestId: (testId: string) => HTMLElement[];
  
  // Optional query methods
  queryByLabelText: (text: string | RegExp) => HTMLElement | null;
  queryByPlaceholderText: (text: string | RegExp) => HTMLElement | null;
  queryByText: (text: string | RegExp) => HTMLElement | null;
  queryByDisplayValue: (text: string | RegExp) => HTMLElement | null;
  queryByAltText: (text: string | RegExp) => HTMLElement | null;
  queryByTitle: (text: string | RegExp) => HTMLElement | null;
  queryByRole: (role: string, options?: any) => HTMLElement | null;
  queryByTestId: (testId: string) => HTMLElement | null;
}

/**
 * Type-safe log collection result
 */
type CollectedLogs<T extends readonly string[]> = {
  [K in T[number]]: string[];
};

/**
 * Default console log function types
 */
type LogFuncs = 'log' | 'warn' | 'error';