Skip to content

Latest commit

 

History

History
313 lines (234 loc) · 9.46 KB

File metadata and controls

313 lines (234 loc) · 9.46 KB
name uui-unit-testing
description Helps write unit tests for UUI components using Jest, jsdom, and @epam/uui-test-utils. Use when writing unit tests for UUI components, updating snapshots, running Jest tests, or working with test utilities.

UUI Unit Testing

UUI uses Jest with jsdom for unit testing React components. Tests are located in __tests__ folders within each package.

Test File Location

Test files follow the pattern: **/__tests__/**/*.test.{js,ts,tsx}

Examples:

  • uui/components/buttons/__tests__/Button.test.tsx
  • uui/components/inputs/__tests__/NumericInput.test.tsx
  • uui-core/src/hooks/__tests__/useVirtualList.test.tsx

Test Commands

Run all tests:

yarn test

Run tests in watch mode:

yarn test-watch

Run a single test file:

yarn test -- --testPathPattern="Button"

Use --testPathPattern to match file path, or --testNamePattern="should render" to match test names.

Update snapshots:

yarn test-update

Generate test coverage report:

yarn test-report

Coverage report saves to .reports/unit-tests folder.

TypeScript type checking:

yarn test-typecheck

Windows workaround: If encountering test errors on Windows, use reduced worker count:

yarn test --maxWorkers=2 --testTimeout=10000

You can increase maxWorkers up to 4 if needed.

Using @epam/uui-test-utils

The @epam/uui-test-utils package provides helpers for testing UUI components with proper context.

Key Exports

Export Purpose
renderSnapshotWithContextAsync Render with UUI context, returns DocumentFragment for snapshots
renderWithContextAsync Render with UUI context for interaction testing
renderHookWithContextAsync Test custom hooks with UUI context
setupComponentForTest Setup component with mocks and setProps helper
screen Re-exported from @testing-library/react
userEvent Re-exported from @testing-library/user-event
fireEvent Re-exported from @testing-library/react
SvgMock Mock for SVG icon imports in snapshots
mockReactPortalsForSnapshots Mock portals for snapshot tests
delay, delayAct Async timing utilities for tests

renderSnapshotWithContextAsync

Renders component with UUI context and returns a DocumentFragment (via asFragment()) for snapshot matching:

import { renderSnapshotWithContextAsync } from '@epam/uui-test-utils';
import { NumericInput } from '../NumericInput';

it('should be rendered with minimum props', async () => {
    const tree = await renderSnapshotWithContextAsync(
        <NumericInput value={null} onValueChange={jest.fn} min={0} max={50} />
    );
    expect(tree).toMatchSnapshot();
});

setupComponentForTest

Use for testing controlled components (e.g. value + onValueChange). Provides setProps to update props without unmounting, and mocks for callback assertions. Call context.current.setProperty(name, value) in callback mocks to simulate controlled updates:

import { setupComponentForTest, screen, fireEvent } from '@epam/uui-test-utils';
import { TextInput } from '@epam/uui';

interface TestComponentProps {
    value?: string;
    onValueChange?: (value: string) => void;
}

const { mocks, setProps } = await setupComponentForTest<TestComponentProps>(
    (context) => ({
        value: 'initial',
        onValueChange: jest.fn().mockImplementation((newValue) => {
            context.current.setProperty('value', newValue);
        }),
    }),
    (props) => <TextInput value={props.value} onValueChange={props.onValueChange} />,
);

const input = screen.queryByRole('textbox');
fireEvent.change(input, { target: { value: 'updated' } });
expect(mocks.onValueChange).toHaveBeenLastCalledWith('updated');

setProps({ value: 'external' }); // Update props without unmounting

renderHookWithContextAsync

Renders a hook with UUI context. Returns { result, rerender, unmount, svc }:

  • result — current hook return value (use result.current)
  • svc — UUI services (api, modals, router, etc.) for mocking or assertions
  • rerender, unmount — same as renderHook from Testing Library
import { renderHookWithContextAsync, act } from '@epam/uui-test-utils';

it('should use DataSource view', async () => {
    const dataSource = useArrayDataSource({ items: [...], getId: (i) => i.id }, []);
    const { result, svc } = await renderHookWithContextAsync(() =>
        dataSource.useView({}, () => {}, {})
    );
    const rows = result.current.getVisibleRows();
    expect(rows).toHaveLength(5);
    // svc.api, svc.uuiModals, etc. available for mocks
});

it('should test custom hook with services', async () => {
    const { result, svc } = await renderHookWithContextAsync(useMyHook);
    act(() => result.current.doSomething());
    expect(svc.uuiAnalytics.sendEvent).toHaveBeenCalled();
});

renderWithContextAsync

Renders component with UUI context for interaction testing:

import { renderWithContextAsync, screen, userEvent } from '@epam/uui-test-utils';
import { Tag } from '../Tag';

it('should call onClear callback', async () => {
    const onClearMock = jest.fn();
    
    await renderWithContextAsync(
        <Tag caption="Test badge" onClear={onClearMock} />
    );
    
    const clearButton = await screen.findByRole('button', { name: /remove tag/i });
    await userEvent.click(clearButton);
    
    expect(onClearMock).toHaveBeenCalled();
});

Test Structure

Basic Snapshot Test

import React from 'react';
import { ComponentName } from '../ComponentName';
import { renderSnapshotWithContextAsync } from '@epam/uui-test-utils';

describe('ComponentName', () => {
    describe('snapshots', () => {
        it('should be rendered with minimum props', async () => {
            const tree = await renderSnapshotWithContextAsync(<ComponentName />);
            expect(tree).toMatchSnapshot();
        });

        it('should be rendered with maximum props', async () => {
            const tree = await renderSnapshotWithContextAsync(
                <ComponentName prop1="value1" prop2="value2" />
            );
            expect(tree).toMatchSnapshot();
        });
    });
});

Interaction Test

import { renderWithContextAsync, screen, userEvent } from '@epam/uui-test-utils';

it('should handle user interaction', async () => {
    const onActionMock = jest.fn();
    
    await renderWithContextAsync(
        <ComponentName onAction={onActionMock} />
    );
    
    const button = await screen.findByRole('button');
    await userEvent.click(button);
    
    expect(onActionMock).toHaveBeenCalled();
});

Snapshot Update Workflow

When UI changes require snapshot updates:

  1. Run tests to see which snapshots fail:

    yarn test
  2. Review changes - verify the visual changes are intentional

  3. Update snapshots:

    yarn test-update
  4. Commit updated snapshots along with component changes

Important: Always review snapshot diffs before updating. Snapshots should reflect intentional UI changes, not accidental regressions.

Test Environment

  • Test framework: Jest
  • Test environment: jsdom (for React components), node (for build scripts)
  • Test utilities: @epam/uui-test-utils for component testing helpers

Test Coverage

Test coverage is collected from:

  • uui-core
  • uui-components
  • uui
  • epam-promo
  • epam-electric
  • loveship

Run yarn test-report to generate coverage reports.

Common Patterns

Mocking Portals for Snapshots

Use the built-in helper instead of manual mocking:

import { mockReactPortalsForSnapshots, renderSnapshotWithContextAsync } from '@epam/uui-test-utils';

describe('ComponentWithPortal', () => {
    mockReactPortalsForSnapshots();

    it('should render correctly', async () => {
        const tree = await renderSnapshotWithContextAsync(<MyModal />);
        expect(tree).toMatchSnapshot();
    });
});

Testing with Icons

Use SvgMock from test utils for icon props in snapshots:

import { SvgMock, renderSnapshotWithContextAsync } from '@epam/uui-test-utils';

it('should render with icon', async () => {
    const tree = await renderSnapshotWithContextAsync(
        <Component icon={ SvgMock } />
    );
    expect(tree).toMatchSnapshot();
});

For specific icon imports (when behavior depends on the icon):

// @ts-expect-error
import { ReactComponent as CalendarIcon } from '@epam-assets/icons/action-calendar-fill.svg';

Best Practices

  1. Add tests for bug fixes - Prevent regressions
  2. Add tests for new functionality - Ensure components work as expected
  3. Update snapshots when UI changes - Keep snapshots current
  4. Run full test suite before committing - yarn test
  5. Use descriptive test names - Make failures easy to understand
  6. Test user interactions - Not just rendering, but behavior

References

  • Test utilities: @epam/uui-test-utils package
  • Example snapshot test: uui/components/inputs/__tests__/NumericInput.test.tsx
  • Example interaction test: uui/components/widgets/__tests__/Tag.test.tsx
  • Example setupComponentForTest: app/src/docs/_examples/testing/__tests__/testComponent.test.tsx
  • Example renderHookWithContextAsync: uui-core/src/data/forms/__tests__/useForm.test.tsx
  • Testing documentation: https://uui.epam.com/documents?id=testing-getting-started&mode=doc&isSkin=null&category=testing