AccessiWeather uses a two-mode test strategy for integration tests:
- Cassette Replay (default): Uses VCR cassettes for deterministic HTTP replay—fast, reliable, no network needed
- Live Mode: Makes real API calls to weather providers—useful for refreshing cassettes and validating API changes
Unit tests run independently without network access.
- Uses VCR cassettes for deterministic HTTP replay
- No network access required
- Fast and reliable for CI
- Run with:
pytest tests/integration/
- Makes real API calls to weather providers
- Useful for: refreshing cassettes, validating API changes, nightly runs
- Run with:
LIVE_WEATHER_TESTS=1 pytest tests/integration/ - Requires API keys for Visual Crossing
# Run all unit tests
pytest tests/ -m "not integration"
# Run integration tests with cassettes (default, fast)
pytest tests/integration/
# Run live integration tests (slow, requires network)
LIVE_WEATHER_TESTS=1 pytest tests/integration/ -v
# Run a specific provider's tests
pytest tests/integration/test_nws_integration.py
# Run all tests with the stable parallel default
pytest
# Run all tests with an explicit worker count
pytest -n 8
# Run tests matching a pattern
pytest -k "test_name" -v
# Run last-failed tests first
pytest --lf --ff| Variable | Description |
|---|---|
LIVE_WEATHER_TESTS=1 |
Enable live API mode (real network calls) |
VISUAL_CROSSING_API_KEY |
Required for live Visual Crossing tests |
HYPOTHESIS_PROFILE |
Set to ci, dev, or thorough for property-based tests |
TOGA_BACKEND=toga_dummy |
Use dummy Toga backend for UI tests |
Cassettes are recorded HTTP interactions stored as YAML files. When tests run in default mode, VCR replays these recordings instead of making real network calls. This ensures:
- Determinism: Same response every time
- Speed: No network latency
- Reliability: No flaky tests from API availability
tests/integration/cassettes/
├── nws/ # National Weather Service API recordings
├── openmeteo/ # Open-Meteo API recordings
├── visual_crossing/ # Visual Crossing API recordings
├── environmental/ # AQI/pollen data recordings
└── cross_provider/ # Multi-provider scenario recordings
When API responses change or cassettes become stale:
# Record new cassettes (adds to existing, preserves unchanged)
LIVE_WEATHER_TESTS=1 pytest tests/integration/ --vcr-record=new_episodes
# Re-record all cassettes (replaces existing completely)
LIVE_WEATHER_TESTS=1 pytest tests/integration/ --vcr-record=all
# Record cassettes for a specific provider
LIVE_WEATHER_TESTS=1 pytest tests/integration/test_nws_integration.py --vcr-record=allCassettes are automatically scrubbed of sensitive data:
- API keys (e.g., Visual Crossing keys)
- Authorization headers
- Session cookies
- Request IDs
Always verify cassettes before committing! Run git diff on cassette files to ensure no secrets leaked through.
- User-Agent required: Must include contact info per NWS policy
- Rate limit: ~500ms between requests recommended
- Caching: Tests cache
/pointslookups to reduce API calls - No API key needed: Free public API
- Rate limits: 600/min, 5000/hour, 10000/day
- No API key needed: Free tier available
- Tests use conservative rate limiting to avoid hitting limits
- API key required: Get a free key at visualcrossing.com
- Strict concurrency:
concurrency=1limit—only one request at a time - 429 errors: Should NOT be retried aggressively; wait for rate limit window
Integration tests use contract-based assertions instead of exact value matching. This approach handles cassette drift gracefully—API responses may change slightly over time, but the contract (data types, ranges, required fields) remains stable.
def test_temperature_forecast(weather_data):
assert weather_data.temperature is not None
assert -100 <= weather_data.temperature <= 150 # Reasonable range
assert weather_data.temperature_unit in ["F", "C"]
assert isinstance(weather_data.humidity, (int, float))
assert 0 <= weather_data.humidity <= 100def test_temperature_forecast(weather_data):
assert weather_data.temperature == 72.5 # Brittle!
assert weather_data.conditions == "Partly Cloudy" # Will break# Required field exists and has correct type
assert data.location is not None
assert isinstance(data.location, str)
# Value within expected range
assert -90 <= data.latitude <= 90
assert -180 <= data.longitude <= 180
# Value in expected set
assert data.status in ["OK", "PENDING", "ERROR"]
# Collection has expected structure
assert len(data.hourly_forecast) > 0
assert all(hasattr(h, "temperature") for h in data.hourly_forecast)The CI pipeline (ci.yml) runs integration tests with cassettes by default:
- No network access needed: Works in isolated CI environments
- Deterministic results: Same pass/fail every run
- Fast execution: No waiting for API responses
For catching API changes early, configure nightly runs with live mode:
# Example GitHub Actions schedule
on:
schedule:
- cron: '0 3 * * *' # 3 AM daily
env:
LIVE_WEATHER_TESTS: 1
VISUAL_CROSSING_API_KEY: ${{ secrets.VISUAL_CROSSING_API_KEY }}Cause: Cassette doesn't exist for this test yet.
Fix: Run with live mode to record it:
LIVE_WEATHER_TESTS=1 pytest tests/integration/test_specific.py --vcr-record=new_episodesCause: API response format changed since cassette was recorded.
Fix: Refresh cassettes with live mode:
LIVE_WEATHER_TESTS=1 pytest tests/integration/ --vcr-record=allCause: Rate limit exceeded.
Fix:
- Reduce test parallelism:
pytest -n 1 - Wait for rate limit window to clear (usually 1-60 minutes)
- For Visual Crossing, ensure only one test runs at a time
Cause: Usually missing cassettes or environment differences.
Fix:
- Ensure all cassettes are committed to git
- Check that cassette paths match between local and CI
- Verify
TOGA_BACKEND=toga_dummyis set for UI tests
Cause: Default profile generates many examples.
Fix: Use the CI profile for faster runs:
HYPOTHESIS_PROFILE=ci pytest tests/ -m hypothesis- Start with cassettes: Write the test assuming cassette mode
- Use contract assertions: Don't assert exact values
- Record cassettes: Run once with
LIVE_WEATHER_TESTS=1 --vcr-record=new_episodes - Verify cassettes: Check for leaked secrets before committing
- Test both modes: Ensure test passes in both cassette and live mode