Skip to content

React Testing Library A11Y

DodaTech 4 min read

title: "Testing Library and jest-axe" weight: 22 description: "Learn how to test React component accessibility with jest-axe and @testing-library: write assertions for WCAG compliance, test component states, integrate into CI/CD, and catch regressions early." date: 2026-06-28 lastmod: 2026-06-28 tags: [accessibility, react]


Jest-axe integrates axe-core accessibility rules into Jest tests, allowing assertions like `expect(container).toHaveNoViolations()` on rendered React components, catching accessibility regressions automatically in unit tests and CI/CD pipelines.

## What You'll Learn

You will install and configure jest-axe, write accessibility tests for React components, test component states, and integrate tests into CI/CD.

## Why It Matters

Unit testing accessibility catches issues at the component level before they reach integration or E2E tests. A component that passes jest-axe assertions is accessible in isolation, reducing the chance of regressions reaching production.

## Real-World Use

A developer modifies a Button component to add a new variant. The unit test `expect(render(<Button />)).toHaveNoViolations()` fails because the new variant does not have sufficient contrast. The developer fixes the color before committing.

## Jest-axe Testing Flow

```mermaid
flowchart TD
  A[Component Renders] --> B[axe-core Scan]
  B --> C{Violations?}
  C -->|Yes| D[Test Fails]
  C -->|No| E[Test Passes]
  D --> F[Fix Component]
  F --> A

Writing Accessibility Tests

Install jest-axe and write tests that assert accessibility compliance.

npm install --save-dev jest-axe @testing-library/react
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);

import Button from './Button';

describe('Button accessibility', () => {
  it('has no violations in default state', async () => {
    const { container } = render(<Button>Click me</Button>);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });

  it('has no violations when disabled', async () => {
    const { container } = render(<Button disabled>Click me</Button>);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });

  it('has no violations when loading', async () => {
    const { container } = render(<Button loading>Click me</Button>);
    const results = await axe(container);
    expect(results).toHaveNoViolations();
  });
});
// Testing interactive components
import { render, fireEvent } from '@testing-library/react';
import { axe } from 'jest-axe';
import Accordion from './Accordion';

describe('Accordion accessibility', () => {
  it('has no violations when collapsed', async () => {
    const { container } = render(
      <Accordion items={[{ title: 'Section', content: 'Content' }]} />
    );
    expect(await axe(container)).toHaveNoViolations();
  });

  it('has no violations when expanded', async () => {
    const { container, getByText } = render(
      <Accordion items={[{ title: 'Section', content: 'Content' }]} />
    );
    fireEvent.click(getByText('Section'));
    expect(await axe(container)).toHaveNoViolations();
  });
});
// Testing with custom rules
import { configureAxe } from 'jest-axe';

const axeWithRules = configureAxe({
  rules: {
    'color-contrast': { enabled: true },
    'region': { enabled: false } // Disable region rule for fragments
  }
});

test('component with custom rules', async () => {
  const { container } = render(<MyComponent />);
  const results = await axeWithRules(container);
  expect(results).toHaveNoViolations();
});

Common Mistakes

  • Not testing all component states (default, hover, focus, disabled, error)
  • Using axe incorrectly with container (axe expects the DOM element, not a React component)
  • Not configuring rules for project-specific needs
  • Ignoring test failures without investigating
  • Not integrating accessibility tests into CI/CD
  • Testing only happy paths without error states
  • Using snapshot tests instead of axe assertions

Practice and Challenge

Practice 1: Install jest-axe and write a test for a Button component. Practice 2: Test a form component in both valid and error states. Practice 3: Test an accordion in collapsed and expanded states. Practice 4: Configure axe rules to exclude known false positives. Practice 5: Integrate jest-axe into a CI/CD pipeline.

Challenge: Create a test suite for a component library with at least 5 components. Each component should be tested in all states (default, hover, focus, active, disabled, error, loading). The test suite should have at least 15 test cases and be configured to run in CI/CD. Include a custom axe configuration that handles project-specific patterns.

FAQ

What is jest-axe?

jest-axe is a Jest matcher that runs axe-core accessibility checks on rendered DOM output.

Do jest-axe tests replace manual testing?

No. They catch automated-detectable issues but cannot verify screen reader announcements or focus order.

How do I configure axe rules?

Use configureAxe to enable/disable specific rules or set options.

Should I test every component?

Test every unique component. Reusable components and form components should always be tested.

How fast are jest-axe tests?

Each axe scan takes 50-200ms. A test suite of 50 tests completes in seconds.

Can I test dynamic content?

Yes. Render the component with different props to test different states.

Mini Project

Create a React component library test suite with jest-axe. Include tests for: Button (default, disabled, loading, icon-only), Input (default, error, disabled, with helper text), Modal (open, with various content), Accordion, and Tabs. Each component should have tests for all states. Configure the test to fail on any violation.

What's Next

eslint-plugin-jsx-a11y covers linting React components for accessibility issues.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro