Files
crypto_trader/docs/developer/frontend_testing.md

7.1 KiB

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:

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:

{
  "scripts": {
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest --coverage"
  }
}

Testing Strategy

Component Testing

Test individual components in isolation:

// 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:

// 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:

// 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:

// 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

# 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
    │   └── ...
    └── ...