Files
crypto_trader/docs/developer/frontend_testing.md

271 lines
7.1 KiB
Markdown

# Frontend Testing Guide
This guide explains how to test the React frontend components and pages.
## Testing Setup
The frontend uses Vitest (recommended) or Jest for testing React components. To set up testing:
```bash
cd frontend
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event vitest @vitest/ui jsdom
```
Add to `package.json`:
```json
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest --coverage"
}
}
```
## Testing Strategy
### Component Testing
Test individual components in isolation:
```typescript
// frontend/src/components/__tests__/StatusIndicator.test.tsx
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import StatusIndicator from '../StatusIndicator';
describe('StatusIndicator', () => {
it('renders connected status correctly', () => {
render(<StatusIndicator status="connected" label="WebSocket" />);
expect(screen.getByText('WebSocket')).toBeInTheDocument();
});
it('shows correct color for error status', () => {
render(<StatusIndicator status="error" label="Error" />);
const chip = screen.getByText('Error');
expect(chip).toHaveClass('MuiChip-colorError');
});
});
```
### Page Testing
Test complete page workflows:
```typescript
// frontend/src/pages/__tests__/StrategiesPage.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { describe, it, expect, vi } from 'vitest';
import StrategiesPage from '../StrategiesPage';
import * as strategiesApi from '../../api/strategies';
vi.mock('../../api/strategies');
describe('StrategiesPage', () => {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } }
});
it('displays list of strategies', async () => {
const mockStrategies = [
{
id: 1,
name: 'Test Strategy',
strategy_type: 'rsi',
enabled: false,
paper_trading: true,
// ... other fields
}
];
vi.mocked(strategiesApi.strategiesApi.listStrategies).mockResolvedValue(mockStrategies);
render(
<QueryClientProvider client={queryClient}>
<StrategiesPage />
</QueryClientProvider>
);
await waitFor(() => {
expect(screen.getByText('Test Strategy')).toBeInTheDocument();
});
});
it('shows create strategy button', () => {
render(
<QueryClientProvider client={queryClient}>
<StrategiesPage />
</QueryClientProvider>
);
expect(screen.getByText('Create Strategy')).toBeInTheDocument();
});
});
```
### API Integration Testing
Test API client functions:
```typescript
// frontend/src/api/__tests__/strategies.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { strategiesApi } from '../strategies';
import { apiClient } from '../client';
vi.mock('../client');
describe('strategiesApi', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('lists strategies', async () => {
const mockStrategies = [{ id: 1, name: 'Test' }];
vi.mocked(apiClient.get).mockResolvedValue({ data: mockStrategies });
const result = await strategiesApi.listStrategies();
expect(result).toEqual(mockStrategies);
expect(apiClient.get).toHaveBeenCalledWith('/api/strategies/');
});
it('creates strategy', async () => {
const newStrategy = { name: 'New', strategy_type: 'rsi' };
const created = { id: 1, ...newStrategy };
vi.mocked(apiClient.post).mockResolvedValue({ data: created });
const result = await strategiesApi.createStrategy(newStrategy);
expect(result).toEqual(created);
});
});
```
### Hook Testing
Test custom React hooks:
```typescript
// frontend/src/hooks/__tests__/useWebSocket.test.ts
import { renderHook, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useWebSocket } from '../useWebSocket';
// Mock WebSocket
global.WebSocket = vi.fn(() => ({
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
send: vi.fn(),
close: vi.fn(),
readyState: WebSocket.OPEN,
})) as any;
describe('useWebSocket', () => {
it('connects to WebSocket', async () => {
const { result } = renderHook(() => useWebSocket('ws://localhost:8000/ws/'));
await waitFor(() => {
expect(result.current.isConnected).toBe(true);
});
});
it('handles messages', async () => {
const { result } = renderHook(() => useWebSocket('ws://localhost:8000/ws/'));
// Simulate message
const ws = (global.WebSocket as any).mock.results[0].value;
const messageEvent = new MessageEvent('message', {
data: JSON.stringify({ type: 'order_update', order_id: 1 })
});
ws.onmessage(messageEvent);
await waitFor(() => {
expect(result.current.lastMessage).toBeTruthy();
});
});
});
```
## Test Coverage
### Components to Test
1. **Pages**:
- `StrategiesPage` - CRUD operations, start/stop
- `TradingPage` - Order placement, position closing
- `DashboardPage` - AutoPilot controls, system health
- `PortfolioPage` - Position management, charts
- `BacktestPage` - Backtest execution, results
- `SettingsPage` - All settings tabs
2. **Components**:
- `StrategyDialog` - Form validation, parameter configuration
- `OrderForm` - Order type handling, validation
- `PositionCard` - Position display, close functionality
- `StatusIndicator` - Status display
- `SystemHealth` - Health status aggregation
- `DataFreshness` - Timestamp calculations
- `OperationsPanel` - Operation display
3. **Hooks**:
- `useWebSocket` - Connection, messages, subscriptions
- `useRealtimeData` - Query invalidation
4. **Contexts**:
- `SnackbarContext` - Notification display
- `WebSocketProvider` - WebSocket management
## Running Tests
```bash
# Run all tests
npm test
# Run tests in watch mode
npm test -- --watch
# Run tests with UI
npm run test:ui
# Generate coverage report
npm run test:coverage
```
## Best Practices
1. **Mock External Dependencies**: Mock API calls, WebSocket connections
2. **Test User Interactions**: Use `@testing-library/user-event` for clicks, typing
3. **Test Error States**: Verify error handling and display
4. **Test Loading States**: Ensure loading indicators appear
5. **Test Accessibility**: Use `@testing-library/jest-dom` matchers
6. **Keep Tests Fast**: Mock expensive operations
7. **Test Edge Cases**: Empty states, error conditions, boundary values
## Example Test Suite Structure
```
frontend/src/
├── components/
│ ├── __tests__/
│ │ ├── StatusIndicator.test.tsx
│ │ ├── StrategyDialog.test.tsx
│ │ └── ...
│ └── ...
├── pages/
│ ├── __tests__/
│ │ ├── StrategiesPage.test.tsx
│ │ ├── TradingPage.test.tsx
│ │ └── ...
│ └── ...
├── hooks/
│ ├── __tests__/
│ │ ├── useWebSocket.test.ts
│ │ └── ...
│ └── ...
└── api/
├── __tests__/
│ ├── strategies.test.ts
│ └── ...
└── ...
```