# Testing Guide This guide covers testing practices for Crypto Trader, including backend API testing, integration testing, and end-to-end testing. ## Test Structure Tests are organized to mirror the source code structure: ``` tests/ ├── unit/ # Unit tests │ ├── backend/ # Backend API tests │ ├── core/ # Core module tests │ └── ... ├── integration/ # Integration tests ├── e2e/ # End-to-end tests ├── fixtures/ # Test fixtures ├── utils/ # Test utilities └── performance/ # Performance benchmarks ``` ## Running Tests ### All Tests ```bash pytest ``` ### With Coverage ```bash pytest --cov=src --cov-report=html ``` ### Specific Test File ```bash pytest tests/unit/core/test_config.py ``` ### Specific Test ```bash pytest tests/unit/core/test_config.py::test_config_loading ``` ### Verbose Output ```bash pytest -v ``` ### Test Categories ```bash # Unit tests only pytest -m unit # Integration tests only pytest -m integration # End-to-end tests only pytest -m e2e ``` ## Writing Tests ### Unit Tests Test individual functions and classes in isolation: ```python import pytest from unittest.mock import Mock, patch from src.core.config import get_config class TestConfig: """Tests for configuration system.""" def test_config_loading(self): """Test configuration loading.""" config = get_config() assert config is not None assert config.config_dir is not None ``` ### Backend API Tests Test FastAPI endpoints using TestClient: ```python from fastapi.testclient import TestClient from backend.main import app client = TestClient(app) def test_get_orders(): """Test getting orders endpoint.""" response = client.get("/api/trading/orders") assert response.status_code == 200 data = response.json() assert isinstance(data, list) def test_place_order(): """Test placing an order.""" order_data = { "exchange_id": 1, "symbol": "BTC/USD", "side": "buy", "type": "market", "quantity": 0.1, "paper_trading": True } response = client.post("/api/trading/orders", json=order_data) assert response.status_code == 201 data = response.json() assert data["symbol"] == "BTC/USD" ``` ### Integration Tests Test component interactions: ```python import pytest from fastapi.testclient import TestClient from backend.main import app @pytest.mark.integration def test_trading_workflow(client: TestClient): """Test complete trading workflow.""" # Place order order_response = client.post("/api/trading/orders", json={...}) assert order_response.status_code == 201 order_id = order_response.json()["id"] # Check order status status_response = client.get(f"/api/trading/orders/{order_id}") assert status_response.status_code == 200 # Check portfolio update portfolio_response = client.get("/api/portfolio/current") assert portfolio_response.status_code == 200 ``` ### End-to-End Tests Test complete user workflows: ```python @pytest.mark.e2e async def test_paper_trading_scenario(): """Test complete paper trading scenario.""" # Test full application flow through API pass ``` ## Test Fixtures Use fixtures for common setup: ```python import pytest from fastapi.testclient import TestClient from backend.main import app from src.core.database import get_database @pytest.fixture def client(): """Test client fixture.""" return TestClient(app) @pytest.fixture def mock_exchange(): """Mock exchange adapter.""" exchange = Mock() exchange.fetch_balance.return_value = {'USD': {'free': 1000}} return exchange @pytest.fixture def test_db(): """Test database fixture.""" # Use in-memory SQLite for unit tests (fast, isolated) # Note: Requires aiosqlite installed as test dependency db = get_database() # Setup test data yield db # Cleanup ``` ## Mocking ### Mocking External APIs ```python from unittest.mock import patch, AsyncMock @patch('src.exchanges.coinbase.ccxt') async def test_coinbase_connection(mock_ccxt): """Test Coinbase connection.""" mock_exchange = AsyncMock() mock_ccxt.coinbaseadvanced.return_value = mock_exchange mock_exchange.load_markets = AsyncMock() adapter = CoinbaseExchange(...) await adapter.connect() assert adapter.is_connected ``` ### Mocking Database ```python @pytest.fixture def test_db(): """Create test database.""" # Use in-memory SQLite for unit tests (internal use only) from src.core.database import Database db = Database("sqlite:///:memory:") db.create_tables() return db ``` ### Mocking Services ```python @pytest.fixture def mock_trading_engine(): """Mock trading engine.""" engine = Mock() engine.execute_order.return_value = MockOrder(id=1, status="filled") return engine def test_place_order_endpoint(mock_trading_engine): """Test order placement with mocked engine.""" with patch('backend.api.trading.get_trading_engine', return_value=mock_trading_engine): response = client.post("/api/trading/orders", json={...}) assert response.status_code == 201 ``` ## Async Testing Use `pytest-asyncio` for async tests: ```python import pytest @pytest.mark.asyncio async def test_async_function(): """Test async function.""" result = await my_async_function() assert result is not None ``` ## WebSocket Testing Test WebSocket endpoints: ```python from fastapi.testclient import TestClient def test_websocket_connection(client: TestClient): """Test WebSocket connection.""" with client.websocket_connect("/ws/") as websocket: # Send message websocket.send_json({"type": "subscribe", "channel": "prices"}) # Receive message data = websocket.receive_json() assert data["type"] == "price_update" ``` ## Test Coverage Target 95% code coverage: ```bash # Generate coverage report pytest --cov=src --cov-report=html # View in browser open htmlcov/index.html ``` ### Coverage Configuration Configure in `pytest.ini`: ```ini [pytest] cov = src cov-report = term-missing cov-report = html cov-fail-under = 95 ``` ## Frontend Testing The frontend uses React Testing Library and Vitest for testing. See [Frontend Testing Guide](./frontend_testing.md) for detailed information. ### Quick Start ```bash cd frontend npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event vitest jsdom npm test ``` ### Component Testing ```typescript 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 '../pages/StrategiesPage'; import * as strategiesApi from '../api/strategies'; vi.mock('../api/strategies'); describe('StrategiesPage', () => { it('renders and displays strategies', async () => { const mockStrategies = [{ id: 1, name: 'Test Strategy', ... }]; vi.mocked(strategiesApi.strategiesApi.listStrategies).mockResolvedValue(mockStrategies); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); render( ); await waitFor(() => { expect(screen.getByText('Test Strategy')).toBeInTheDocument(); }); }); }); ``` ### Testing New Components All new components should have corresponding test files: - `StrategiesPage` → `StrategiesPage.test.tsx` - `TradingPage` → `TradingPage.test.tsx` - `StrategyDialog` → `StrategyDialog.test.tsx` - `OrderForm` → `OrderForm.test.tsx` - And all other new components See [Frontend Testing Guide](./frontend_testing.md) for comprehensive testing patterns. ## Best Practices 1. **Test Independence**: Tests should not depend on each other 2. **Fast Tests**: Unit tests should run quickly (< 1 second each) 3. **Clear Names**: Test names should describe what they test 4. **One Assertion**: Prefer one assertion per test when possible 5. **Mock External**: Mock external dependencies (APIs, databases) 6. **Test Edge Cases**: Test boundary conditions and errors 7. **Documentation**: Document complex test scenarios 8. **Arrange-Act-Assert**: Structure tests clearly 9. **Use Fixtures**: Reuse common setup code 10. **Isolation**: Each test should be able to run independently ## Test Organization ### Unit Tests - Test single function/class - Mock external dependencies - Fast execution - High coverage ### Integration Tests - Test component interactions - Use test database - Test real workflows - Moderate speed ### E2E Tests - Test complete user flows - Use test environment - Slow execution - Critical paths only ## Continuous Integration Tests run automatically in CI/CD: - All tests must pass - Coverage must meet threshold (95%) - Code style must pass - Type checking must pass (if using mypy) ## Debugging Tests ### Verbose Output ```bash pytest -vv # Very verbose pytest -s # Show print statements ``` ### Debugging Failed Tests ```bash # Drop into debugger on failure pytest --pdb # Drop into debugger on first failure pytest -x --pdb ``` ### Running Last Failed Tests ```bash pytest --lf # Last failed pytest --ff # Failed first ```