Frontend - Development
Frontend Testing
Tools and Setup
Test Framework
- Vitest for unit and integration tests
- React Testing Library for component testing
- Storybook for component development and visual testing
- MSW (Mock Service Worker) for API mocking
Component Testing
Button Component Example
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '@/components/ui/button';
describe('Button', () => {
it('renders with correct text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('handles click events', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalled();
});
});
Form Testing
describe('CreateProjectForm', () => {
const mockOnClose = vi.fn();
beforeEach(() => {
render(<CreateProjectForm onClose={mockOnClose} />);
});
it('validates required fields', async () => {
const submitButton = screen.getByText('Next');
fireEvent.click(submitButton);
expect(await screen.findByText('Authorization is required')).toBeInTheDocument();
expect(await screen.findByText('Repository is required')).toBeInTheDocument();
});
it('handles multi-step form navigation', async () => {
// Fill first step
await selectAuthorization('github');
await selectRepository('user/repo');
const nextButton = screen.getByText('Next');
fireEvent.click(nextButton);
// Verify second step is shown
expect(screen.getByText('Resource Configuration')).toBeInTheDocument();
});
});
Redux Testing
Store Setup
const createTestStore = (initialState = {}) => {
return configureStore({
reducer: {
auth: authReducer,
config: configReducer,
},
preloadedState: initialState,
});
};
const renderWithStore = (
ui: React.ReactElement,
initialState = {}
) => {
const store = createTestStore(initialState);
return {
...render(<Provider store={store}>{ui}</Provider>),
store,
};
};
Redux Hook Testing
describe('useStore', () => {
it('provides access to auth state', () => {
const { result } = renderHook(() => useStore(), {
wrapper: createWrapper({
auth: { isAuth: true, token: 'test-token' }
})
});
expect(result.current.state.auth.isAuth).toBe(true);
expect(result.current.state.auth.token).toBe('test-token');
});
});
API Mocking
MSW Setup
import { setupServer } from 'msw/node';
import { rest } from 'msw';
const server = setupServer(
rest.get('/api/projects', (req, res, ctx) => {
return res(ctx.json([
{ id: '1', name: 'Test Project' }
]));
}),
rest.post('/api/projects', (req, res, ctx) => {
return res(ctx.json({ id: '2', ...req.body }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Testing API Interactions
describe('ProjectsList', () => {
it('displays projects from API', async () => {
render(<ProjectsList />);
// Wait for data to load
await screen.findByText('Test Project');
// Verify project card is displayed
expect(screen.getByText('Test Project')).toBeInTheDocument();
});
it('handles API errors', async () => {
server.use(
rest.get('/api/projects', (req, res, ctx) => {
return res(ctx.status(500));
})
);
render(<ProjectsList />);
// Verify error message is displayed
expect(await screen.findByText('Error loading projects')).toBeInTheDocument();
});
});
Storybook Tests
Component Stories
import type { Meta, StoryObj } from '@storybook/react';
import { ProjectCard } from './ProjectCard';
const meta: Meta<typeof ProjectCard> = {
component: ProjectCard,
args: {
project: {
id: '1',
name: 'Test Project',
status: 'running',
},
},
};
export default meta;
type Story = StoryObj<typeof ProjectCard>;
export const Default: Story = {};
export const Loading: Story = {
args: {
project: {
...meta.args.project,
status: 'loading',
},
},
};
Visual Regression Testing
describe('ProjectCard', () => {
it('visual regression test', async () => {
const canvas = within(canvasElement);
// Take snapshot for comparison
await expect(canvas).toMatchImageSnapshot();
});
});
Integration Testing
Router Testing
describe('App Routing', () => {
it('redirects unauthenticated users', () => {
render(
<MemoryRouter initialEntries={['/dashboard']}>
<App />
</MemoryRouter>
);
expect(screen.getByText('Login')).toBeInTheDocument();
});
it('allows authenticated access', () => {
const { store } = renderWithStore(
<MemoryRouter initialEntries={['/dashboard']}>
<App />
</MemoryRouter>,
{
auth: { isAuth: true, token: 'valid-token' }
}
);
expect(screen.getByText('Projects')).toBeInTheDocument();
});
});
Test Best Practices
Component Testing
- Test user interactions
- Verify state changes
- Check error handling
- Test accessibility
Integration Testing
- Test component combinations
- Verify data flow
- Check routing behavior
- Test real-world scenarios
Visual Testing
- Capture component variations
- Test responsive behavior
- Check theme changes
- Verify animations