Table of Contents
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.

The test targets are the following two areas:
- View (Business Logic driven by Props and State)
Props, State => Business Logic => View - 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:
- Verify View by changing Props
- Change Props, State (State initialized by Props) => Business Logic => Verify View
- 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
- 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
| Jest | Storybook | Vitest | |
|---|---|---|---|
| View verification with Props (VRT) | O (VRT tests must be in separate files) | O (Different approach from product test code) | O |
| Event testing | O | O | O |
| Accessibility testing | O | O | O |
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
Deku.Deku created the applications with Flutter.If you have interested, please try to download them for free.