[React] Testing Strategy for Shared Components in a Monorepo

2026-03-05 hit count image

Sharing the process of establishing a testing strategy for React shared components in a monorepo. We compare Jest, Storybook, and Vitest for View testing with Props/State, event testing, and accessibility testing, and explain why we ultimately chose Vitest.

react

Overview

As our team and products grew, other departments began using our monorepo as a shared platform. To ensure shared components could be modified easily and safely, we decided to establish a testing strategy and introduce test code for shared components.

There were already some tests for shared components, but they had been written without a clear strategy, resulting in inconsistent content. We aimed to unify the testing standards by establishing a clear strategy.

What to Test in Components

React components consist of Props, State, the View rendered based on Props and State, and Events that handle user interactions with the rendered View.

Component Test Targets

The test targets are the following two areas:

  1. View (Business Logic driven by Props and State)
    Props, State => Business Logic => View
  2. Event (Business Logic within Event Handlers)
    Event => Business Logic => State => Business Logic => View
    Event => Business Logic => Parent Event Handler

We need to establish a testing strategy to effectively test these areas.

Testing Strategy

Based on the test targets, we established the following testing strategy:

  1. Verify View by changing Props
    • Change Props, State (State initialized by Props) => Business Logic => Verify View
  2. Verify View or Event Handler by triggering events
    • Fire Event => Business Logic => State => Business Logic => Verify View
    • Fire Event => Business Logic => Verify Parent Event Handler
  3. Accessibility testing

Testing Methods

Component tests can be written using Jest, Storybook Interaction tests, or Vitest. We decided to examine each approach and choose the one best suited for our team.

Jest

For details on building a VRT environment with Jest + Puppeteer, see [React] Building VRT with Jest + Puppeteer.

1. Verify View by Changing Props

We use snapshot testing to verify the View when Props change.

it('should match snapshot for primary variant', async () => {
  await expectToMatchVRTSnapshot(
    <Button variant="primary">Primary Button</Button>,
    'button-primary'
  );
});

2. Event Testing

We trigger events and verify the resulting state changes and View updates.

test('displays custom fetchErrorText', async () => {
  const error = new Error('Network error');
  mockFetchChildNodesFunction.mockRejectedValueOnce(error);

  const customErrorText = {
    title: 'Custom Error',
    description: 'Custom Description',
    btnLabel: 'Retry',
  };

  render(
    <ExpandableTreeList
      topTreeNodes={sampleTopTreeNodes}
      fetchErrorText={customErrorText}
      fetchChildNodesFunction={mockFetchChildNodesFunction}
    />
  );

  const expandButton = screen.getAllByRole('button')[0];
  fireEvent.click(expandButton);

  await waitFor(() => {
    expect(screen.getByText('Custom Error')).toBeInTheDocument();
    expect(screen.getByText('Custom Description')).toBeInTheDocument();
    expect(screen.getByText('Retry')).toBeInTheDocument();
  });
});

3. Accessibility Testing (jest-axe)

test('has no accessibility violations', async () => {
  const { container } = render(
    <ExpandableTreeList
      topTreeNodes={sampleTopTreeNodes}
      sectionLabel="Accessibility Test"
      countLabel="${count} items"
      fetchChildNodesFunction={mockFetchChildNodesFunction}
      onRemove={mockOnRemove}
    />
  );

  const results = await axe(container, {
    rules: {
      'button-name': { enabled: false },
    },
  });
  expect(results).toHaveNoViolations();
});

Storybook

For details on building VRT + accessibility testing with Storybook Test Runner, see [React] Building VRT + Accessibility Testing with Storybook Test Runner.

1. Verify View by Changing Props (Storybook Snapshot)

export const RenderingTopTreeNodes: Story = {
  name: 'Rendering: Display Tree Nodes',
  render: () => (
    <div style={{ height: '25rem' }}>
      <ExpandableTreeList
        topTreeNodes={sampleTopTreeNodes}
        fetchChildNodesFunction={createMockFetchFunction()}
      />
    </div>
  ),
  parameters: {
    chromatic: { disableSnapshot: false },
  },
};

2. Event Testing (Storybook Interaction)

export const EventDisplayChildNodes: Story = {
  name: 'Event: Display Child Nodes After Fetch',
  render: () => (
    <div style={{ height: '25rem' }}>
      <ExpandableTreeList
        topTreeNodes={sampleTopTreeNodes}
        fetchChildNodesFunction={createMockFetchFunction()}
      />
    </div>
  ),
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    const expandButton = canvas.getAllByRole('button')[0];
    await userEvent.click(expandButton);

    await new Promise((resolve) => setTimeout(resolve, 100));

    expect(canvas.getByText('Child 1')).toBeInTheDocument();
    expect(canvas.getByText('Child 2')).toBeInTheDocument();
  },
};

3. Accessibility Testing (Storybook a11y)

export const A11yTest: Story = {
  name: 'Accessibility: Accessibility Test',
  tags: ['a11y-test'],
  render: () => (
    <div style={{ height: '25rem' }}>
      <ExpandableTreeList
        topTreeNodes={sampleTopTreeNodes}
        sectionLabel="Accessibility Test"
        countLabel="${count} items"
        fetchChildNodesFunction={createMockFetchFunction()}
        onRemove={fn()}
      />
    </div>
  ),
  parameters: {
    chromatic: {
      disableSnapshot: true,
    },
    a11y: {
      config: {
        rules: [
          {
            id: 'button-name',
            enabled: false,
          },
        ],
      },
    },
  },
};

Vitest

For details on building VRT + accessibility testing with Vitest, see [React] Building Component VRT + Accessibility Testing with Vitest.

1. Verify View by Changing Props

Vitest natively supports VRT (Visual Regression Testing).

it('renders with text content', async () => {
  render(<Button>Click me</Button>);
  await expect
    .element(page.getByRole('button'))
    .toMatchScreenshot('button-children-text.png');
});

2. Event Testing

it('calls onClick handler when clicked', async () => {
  const onClick = vi.fn();
  render(<Button onClick={onClick}>Click me</Button>);

  await userEvent.click(screen.getByRole('button'));
  expect(onClick).toHaveBeenCalledTimes(1);
});

3. Accessibility Testing (axe-core)

it('should have no accessibility violations', async () => {
  const { container } = render(<Button>Accessible Button</Button>);
  const results = await axe.run(container, axeConfig);
  expect(results.violations).toHaveLength(0);
});

Comparison and Conclusion

JestStorybookVitest
View verification with Props (VRT)O (VRT tests must be in separate files)O (Different approach from product test code)O
Event testingOOO
Accessibility testingOOO

Conclusion: Why We Chose Vitest

All three methods supported View verification with Props changes, event testing, and accessibility testing. However, we determined that Vitest was the most suitable for the following reasons:

  • Storybook: The approach differs from product test code, and Story content becomes bloated, making it difficult to maintain. Storybook is better suited for design and implementation review purposes.
  • Jest: VRT testing is possible, but there is a constraint of needing separate test files.
  • Vitest: It natively provides VRT and uses the same technology stack as product-level View tests, making it the most suitable choice.

Therefore, our team decided to write shared component test code using Vitest.

Reference: Vitest - Visual Regression Testing

Summary

We shared the process of establishing a testing strategy for React shared components in a monorepo environment. We compared View testing with Props/State, event testing, and accessibility testing using Jest, Storybook, and Vitest, and shared why we ultimately chose Vitest.

Going forward, we expect that test code written with Vitest will enable other teams to modify shared components easily and safely. Additionally, with clear standards for writing test code, consistent tests will be produced.

For a concrete guide on gradually introducing Vitest to an existing Jest project, see [React] Introducing Vitest to a Jest Project - View Test Separation Strategy. For building a VRT + accessibility testing environment with Vitest, check out [React] Building Component VRT + Accessibility Testing with Vitest.

By establishing a testing strategy like this, you can also leverage generative AI to easily write test code. We encourage you to define a testing strategy that fits your team and use generative AI to write test code quickly and easily!

Was my blog helpful? Please leave a comment at the bottom. it will be a great help to me!

App promotion

You can use the applications that are created by this blog writer Deku.
Deku created the applications with Flutter.

If you have interested, please try to download them for free.



SHARE
Twitter Facebook RSS