Skip to content

πŸ§ͺ Testing Guide - Economic DashboardΒΆ

This guide explains the testing strategy for the Economic Dashboard and how to run tests.

OverviewΒΆ

All tests are hermetic - they don't require: - ❌ Network access - ❌ API keys - ❌ External databases - ❌ Running services

Tests use mocks (Go) and fixtures (Python) for complete isolation.

Running TestsΒΆ

Run All TestsΒΆ

bazel test //...

Run Specific TestsΒΆ

# Go tests
bazel test //go_fetch:fred_test

# Python tests
bazel test //py_api:api_test

# Legacy services
bazel test //go_service:go_service_test
bazel test //py_service:py_service_test

Verbose OutputΒΆ

# Show all output
bazel test //... --test_output=all

# Show only errors
bazel test //... --test_output=errors

# Show test summary
bazel test //... --test_summary=detailed

Go Tests (go_fetch/)ΒΆ

What's TestedΒΆ

  • βœ… FRED API client (series metadata fetching)
  • βœ… FRED API client (observations fetching)
  • βœ… Error handling for invalid responses
  • βœ… Data filtering (skipping "." values)

Testing Strategy: Mock HTTP ClientΒΆ

The fred_test.go uses a mock HTTP client that returns predefined responses:

type mockHTTPClient struct {
    responses map[string]*http.Response
}

func (m *mockHTTPClient) Get(url string) (*http.Response, error) {
    if resp, ok := m.responses[url]; ok {
        return resp, nil
    }
    return &http.Response{StatusCode: 404}, nil
}

This allows testing without making real API calls.

Example TestΒΆ

func TestFREDClient_FetchSeriesMetadata(t *testing.T) {
    mockClient := &mockHTTPClient{
        responses: map[string]*http.Response{
            "https://api.stlouisfed.org/...": newMockResponse(200, `{
                "seriess": [{
                    "id": "CPIAUCSL",
                    "title": "Consumer Price Index",
                    "units": "Index 1982-1984=100"
                }]
            }`),
        },
    }

    client := NewFREDClientWithHTTP("test", mockClient)
    series, err := client.fetchSeriesMetadata("CPIAUCSL")

    // Assertions...
}

Run Go TestsΒΆ

bazel test //go_fetch:fred_test --test_output=all

Python Tests (py_api/)ΒΆ

What's TestedΒΆ

  • βœ… Database operations (series, observations)
  • βœ… Delta calculations (MoM, YoY)
  • βœ… API endpoints (health, summary, series, meta)
  • βœ… Error handling (missing series, invalid data)

Testing Strategy: Fixture DatabaseΒΆ

The test_api.py creates a temporary SQLite database with test data:

@pytest.fixture
def test_db():
    """Create a test database with sample data."""
    fd, db_path = tempfile.mkstemp(suffix=".db")
    os.close(fd)

    conn = sqlite3.connect(db_path)
    cursor = conn.cursor()

    # Create schema
    cursor.execute("CREATE TABLE series (...)")
    cursor.execute("CREATE TABLE observations (...)")

    # Insert test data
    cursor.execute("INSERT INTO series VALUES (...)")
    cursor.execute("INSERT INTO observations VALUES (...)")

    conn.commit()
    conn.close()

    yield db_path

    # Cleanup
    Path(db_path).unlink(missing_ok=True)

Example TestΒΆ

def test_api_get_summary(test_db, monkeypatch):
    """Test summary endpoint."""
    monkeypatch.setattr("py_api.main.db", EconDatabase(test_db))

    client = TestClient(app)
    response = client.get("/api/econ/summary")

    assert response.status_code == 200
    data = response.json()
    assert data["count"] == 2
    assert len(data["indicators"]) == 2

Run Python TestsΒΆ

bazel test //py_api:api_test --test_output=all

Test CoverageΒΆ

Go Tests CoverageΒΆ

  • fred.go: FRED API client functions
  • FetchSeries() - Integration test
  • fetchSeriesMetadata() - Metadata fetching
  • fetchObservations() - Observations fetching
  • Error handling for bad responses

  • Not tested (integration-level):

  • main.go - CLI entry point
  • store_sqlite.go - SQLite operations

These are tested manually via bazel run //go_fetch:refresh

Python Tests CoverageΒΆ

  • db.py: Database layer
  • get_all_series() - All series
  • get_latest_observation() - Latest value
  • calculate_delta_mom() - MoM delta
  • calculate_delta_yoy() - YoY delta

  • handlers.py: API handlers

  • get_summary() - Summary logic
  • get_series_data() - Series with range

  • main.py: FastAPI endpoints

  • GET /api/health
  • GET /api/econ/summary
  • GET /api/econ/series
  • GET /api/econ/meta

Adding New TestsΒΆ

Adding Go TestsΒΆ

  1. Create test file: go_fetch/myfeature_test.go
  2. Write test with mock HTTP:
    func TestMyFeature(t *testing.T) {
        // Setup mock
        // Call function
        // Assert results
    }
    
  3. Add to BUILD.bazel:
    go_test(
        name = "myfeature_test",
        srcs = ["myfeature_test.go"],
        embed = [":go_fetch_lib"],
    )
    

Adding Python TestsΒΆ

  1. Add test function to py_api/test_api.py:
    def test_my_feature(test_db, monkeypatch):
        # Setup
        monkeypatch.setattr("py_api.main.db", EconDatabase(test_db))
    
        # Test
        # Assert
    
  2. Tests are automatically discovered by pytest

Debugging TestsΒΆ

Show Full OutputΒΆ

bazel test //py_api:api_test --test_output=all

Run Tests Directly (Outside Bazel)ΒΆ

# Go tests
cd go_fetch
go test -v ./...

# Python tests
cd py_api
python -m pytest test_api.py -v

Common IssuesΒΆ

"No module named 'py_api'" - Solution: Run via Bazel which sets up paths correctly - Or: PYTHONPATH=. pytest py_api/test_api.py

"Database not found" - Solution: Tests create their own fixtures, check the test_db fixture

"Import error in Go tests" - Solution: Make sure embed = [":go_fetch_lib"] in BUILD.bazel

CI/CD IntegrationΒΆ

Tests run automatically in GitHub Actions:

- name: Run all tests
  run: bazel test //...

- name: Run Go fetch tests
  run: bazel test //go_fetch:fred_test

- name: Run Python API tests
  run: bazel test //py_api:api_test

See .github/workflows/ci.yaml for full configuration.

Best PracticesΒΆ

  1. βœ… Keep tests hermetic - No network, no external state
  2. βœ… Use fixtures/mocks - Fast and reliable
  3. βœ… Test business logic - Not infrastructure
  4. βœ… Name tests descriptively - test_api_get_summary_returns_indicators
  5. βœ… Clean up resources - Use defer (Go) or fixtures (Python)

Further ReadingΒΆ