TextDAO Frontend Test Strategy
This document outlines the testing strategy for the TextDAO frontend application. It covers the types of tests we write, tools we use, and best practices for ensuring the reliability and correctness of our frontend code, with a focus on Vitest and Storybook.
Testing Framework and Tools
- Vitest: For unit and integration tests
- React Testing Library: For component testing
- Storybook: For UI component development, testing, and documentation
Types of Tests
- Unit Tests: Test individual functions and utilities in isolation.
- Component Tests: Test React components in isolation.
- Integration Tests: Test the interaction between multiple components or hooks.
- Visual Tests: Test the appearance of UI components using Storybook.
Test Structure
Our test files are located alongside the code they are testing and follow this naming convention:
[name].test.ts(x)
For Storybook story files, we use:
[ComponentName].stories.tsx
Writing Tests
Unit Tests
For pure functions and utilities using Vitest:
import { describe, it, expect } from 'vitest';
import { calculateVoteWeight } from './voteUtils';
describe('calculateVoteWeight', () => {
it('returns correct weight for valid input', () => {
expect(calculateVoteWeight(100, 1000)).toBe(0.1);
});
it('returns 0 for 0 total votes', () => {
expect(calculateVoteWeight(100, 0)).toBe(0);
});
});
Component Tests
Using React Testing Library with Vitest:
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { ProposalCard } from './ProposalCard';
describe('ProposalCard', () => {
it('renders proposal details correctly', () => {
render(<ProposalCard id="1" title="Test Proposal" />);
expect(screen.getByText('Test Proposal')).toBeDefined();
});
it('calls onVote when vote button is clicked', () => {
const mockOnVote = vi.fn();
render(<ProposalCard id="1" title="Test Proposal" onVote={mockOnVote} />);
fireEvent.click(screen.getByText('Vote'));
expect(mockOnVote).toHaveBeenCalledWith('1');
});
});
Storybook for Visual Testing
Example of a Storybook story for a component:
import type { Meta, StoryObj } from '@storybook/react';
import { ProposalCard } from './ProposalCard';
const meta: Meta<typeof ProposalCard> = {
component: ProposalCard,
title: 'Components/ProposalCard',
};
export default meta;
type Story = StoryObj<typeof ProposalCard>;
export const Default: Story = {
args: {
id: '1',
title: 'Test Proposal',
},
};
export const LongTitle: Story = {
args: {
id: '2',
title: 'This is a very long proposal title that might wrap to multiple lines',
},
};
Best Practices
- Test Coverage: Aim for high test coverage, especially for critical paths in the application.
- Arrange-Act-Assert: Structure tests using the Arrange-Act-Assert pattern for clarity.
- Meaningful Assertions: Write assertions that test meaningful outcomes, not implementation details.
- Mocking: Use Vitest's built-in mocking capabilities for external dependencies when necessary.
- Accessibility Testing: Use Storybook's accessibility addon to test for accessibility.
- Performance Testing: Implement performance tests for critical components using Storybook's performance addon.
Continuous Integration
Integrate frontend tests into the CI/CD pipeline:
- Run unit and component tests on every pull request.
- Run integration tests on feature branches before merging to main.
- Run Storybook visual tests periodically (e.g., nightly).
- Generate and store test coverage reports.
Testing Hooks
For testing custom hooks, use the @testing-library/react-hooks package:
import { renderHook, act } from '@testing-library/react-hooks';
import { describe, it, expect } from 'vitest';
import { useProposal } from './useProposal';
describe('useProposal', () => {
it('fetches proposal data', async () => {
const { result, waitForNextUpdate } = renderHook(() => useProposal('1'));
await waitForNextUpdate();
expect(result.current.proposal).toEqual({ id: '1', title: 'Test Proposal' });
});
});
Visual Regression Testing
Consider implementing visual regression testing using Chromatic with Storybook:
# In your CI pipeline
npm run build-storybook
npx chromatic --project-token=<your-project-token>
Accessibility Testing
Incorporate accessibility tests using Storybook's accessibility addon:
import { withA11y } from '@storybook/addon-a11y';
export default {
title: 'Components/ProposalCard',
component: ProposalCard,
decorators: [withA11y],
};
Performance Testing
Implement performance tests for critical components:
import { withPerformance } from 'storybook-addon-performance';
export default {
title: 'Components/ProposalList',
component: ProposalList,
decorators: [withPerformance],
};
export const LargeList = {
args: {
proposals: Array.from({ length: 100 }, (_, i) => ({
id: `${i}`,
title: `Proposal ${i}`,
})),
},
};
Troubleshooting
Common testing issues and their solutions:
- Asynchronous Updates: Use
findByqueries for elements that appear after asynchronous operations. - State Updates: Wrap state updates in
act()when testing hooks or components that cause state changes. - Context Providers: Ensure that components under test are wrapped with necessary context providers.
Resources
By following this test strategy, we can ensure the reliability, correctness, and maintainability of the TextDAO frontend application. Regular review and updates to this strategy will help keep our testing practices aligned with the evolving needs of the project.