π§ͺ 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ΒΆ
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ΒΆ
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ΒΆ
Test CoverageΒΆ
Go Tests CoverageΒΆ
- fred.go: FRED API client functions
FetchSeries()- Integration testfetchSeriesMetadata()- Metadata fetchingfetchObservations()- Observations fetching-
Error handling for bad responses
-
Not tested (integration-level):
main.go- CLI entry pointstore_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 seriesget_latest_observation()- Latest valuecalculate_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/healthGET /api/econ/summaryGET /api/econ/seriesGET /api/econ/meta
Adding New TestsΒΆ
Adding Go TestsΒΆ
- Create test file:
go_fetch/myfeature_test.go - Write test with mock HTTP:
- Add to
BUILD.bazel:
Adding Python TestsΒΆ
- Add test function to
py_api/test_api.py: - Tests are automatically discovered by pytest
Debugging TestsΒΆ
Show Full OutputΒΆ
Run Tests Directly (Outside Bazel)ΒΆ
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ΒΆ
- β Keep tests hermetic - No network, no external state
- β Use fixtures/mocks - Fast and reliable
- β Test business logic - Not infrastructure
- β
Name tests descriptively -
test_api_get_summary_returns_indicators - β
Clean up resources - Use
defer(Go) or fixtures (Python)