Skip to main content
Component unit tests use the Node.js built-in test runner (node:test) and @testing-library/react. No Jest or Vitest — the test runner ships with Node.js itself.

File location

Tests live next to the component they cover, inside a __tests__ directory:
MyComponent/
├── index.tsx
├── index.module.css
└── __tests__/
    └── index.test.mjs   ← tests here
Test files use the .mjs extension and ES module syntax. This matches the "type": "module" setting across the packages.

Imports

import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { render, screen, fireEvent } from '@testing-library/react';
import Button from '../index.js';
ImportSource
assertNode.js built-in — strict assertion mode
describe, itNode.js built-in node:test
render, screen, fireEvent@testing-library/react
Component under testRelative path from __tests__/

Writing a test

// __tests__/index.test.mjs
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { render, screen, fireEvent } from '@testing-library/react';
import Button from '../index.js';

describe('Button Component', () => {
  it('renders with correct text', () => {
    render(<Button>Click me</Button>);
    assert(screen.getByRole('button', { name: /click me/i }));
  });

  it('applies variant styles correctly', () => {
    render(<Button variant="secondary">Test</Button>);
    const button = screen.getByRole('button');
    assert(button.getAttribute('class').includes('secondary'));
  });
});

Running tests

pnpm test
The test command used under the hood:
cross-env NODE_NO_WARNINGS=1 node \
  --experimental-test-coverage \
  --test-coverage-exclude=**/*.test.* \
  --experimental-test-module-mocks \
  --enable-source-maps \
  --import=global-jsdom/register \
  --import=tsx \
  --import=tests/setup.jsx \
  --test **/*.test.*
  • --experimental-test-coverage — generates coverage reports
  • --experimental-test-module-mocks — enables module mocking (used to mock next-intl)
  • global-jsdom/register — provides a browser-like DOM environment
  • tsx — enables TypeScript imports without pre-compilation
  • tests/setup.jsx — shared test setup (mocks next-intl, configures testing-library)

What to test

Verify the component renders its content and key elements are accessible by role or text:
it('renders the title', () => {
  render(<Card title="Hello" />);
  assert(screen.getByText('Hello'));
});
Confirm that different prop values produce the expected output:
it('applies the highlighted variant class', () => {
  render(<Card title="X" variant="highlighted" />);
  const card = screen.getByRole('article');
  assert(card.className.includes('highlighted'));
});
Use fireEvent or @testing-library/user-event to simulate clicks and input:
it('calls onClick when clicked', () => {
  let clicked = false;
  render(<Button onClick={() => { clicked = true; }}>Go</Button>);
  fireEvent.click(screen.getByRole('button'));
  assert.equal(clicked, true);
});
Check that elements appear or are absent based on props:
it('does not render description when omitted', () => {
  render(<MyComponent title="T" />);
  assert.equal(screen.queryByText(/description/i), null);
});

Coverage

Coverage output is printed to the console after each run when using --experimental-test-coverage. Coverage for test files themselves is excluded via --test-coverage-exclude=**/*.test.*.

Storybook

Visual component development and interactive stories.

Visual regression

Automated screenshot comparison with Chromatic.

Build docs developers (and LLMs) love