Write and run tests for the python-bsblan library. Use this skill when creating unit tests, working with fixtures, or ensuring code coverage requirements are met.
This skill guides you through testing practices for the python-bsblan library.
Tests are located in tests/ and use pytest with async support.
import pytest
from bsblan import BSBLAN
@pytest.mark.asyncio
async def test_feature_name(mock_bsblan: BSBLAN) -> None:
"""Test description."""
# Arrange
expected_value = "expected"
# Act
result = await mock_bsblan.some_method()
# Assert
assert result == expected_value
Test fixtures (JSON responses) are in tests/fixtures/. Common fixtures:
device.json - Device informationstate.json - Current statehot_water_state.json - Hot water statesensor.json - Sensor readingsLoad fixtures using load_fixture(filename: str) -> str from tests/__init__.py.
import json
from tests import load_fixture
raw = load_fixture("device.json")
data = json.loads(raw) # parsed dict[str, Any]
# Full coverage report
uv run pytest --cov=src/bsblan --cov-report=term-missing
# Coverage for specific test file (useful during development)
uv run pytest tests/test_your_file.py --cov=src/bsblan --cov-report=term-missing --cov-fail-under=0
# HTML report for detailed analysis
uv run pytest --cov=src/bsblan --cov-report=html
# Then open htmlcov/index.html in browser
After adding new methods, always verify coverage:
--cov-report=term-missingline->branch like 382->386)Example output showing good coverage:
src/bsblan/bsblan.py 426 0 170 2 99% 382->386, 1393->1391
The 382->386 notation means line 382's branch to line 386 isn't covered (an edge case).
CI enforces:
If CI fails with coverage issues, check the Codecov report in the PR for uncovered lines.
If a line is genuinely untestable (for example defensive guards), mark it with
# pragma: no cover and justify that choice in the PR description.
# Run all tests
uv run pytest
# Run specific test file
uv run pytest tests/test_bsblan.py
# Run with verbose output
uv run pytest -v
# Run specific test
uv run pytest tests/test_bsblan.py::test_function_name
Always run before committing:
uv run prek run --all-files
This runs:
For API calls, use mock_bsblan fixture and verify calls:
mock_bsblan._request.assert_awaited_with(
base_path="/JS",
data={"Parameter": "1610", "Value": "60.0", "Type": "1"},
)
Define shared fixtures in tests/conftest.py so pytest auto-discovers them.
Use the mock_bsblan pattern for naming and setup:
@pytest.fixture
async def mock_bsblan(
aresponses: ResponsesMockServer,
monkeypatch: Any,
) -> AsyncGenerator[BSBLAN, Any]:
...
Add new JSON payloads in tests/fixtures/ with descriptive, snake_case names
that match the behavior under test.
Use monkeypatch with AsyncMock to return fixture payloads when testing
response parsing logic (not only outgoing request arguments):
import json
from unittest.mock import AsyncMock
from tests import load_fixture
request_mock = AsyncMock(return_value=json.loads(load_fixture("state.json")))
monkeypatch.setattr(bsblan, "_request", request_mock)
state = await bsblan.state()
assert state.current_temperature is not None
When testing hot water methods, mark param groups as validated to skip network calls:
@pytest.mark.asyncio
async def test_hot_water_no_params_error(monkeypatch: Any) -> None:
"""Test error when no parameters available."""
bsblan = BSBLAN(config, session=session)
# Set empty cache and mark group as validated
bsblan.set_hot_water_cache({})
bsblan._validated_hot_water_groups.add("essential") # Skip validation
with pytest.raises(BSBLANError, match="No essential hot water"):
await bsblan.hot_water_state()
For full integration tests with mocked responses:
# Mark group as validated to use cached params
bsblan._validated_hot_water_groups.add("config")
bsblan.set_hot_water_cache({"1601": "eco_mode_selection", ...})
The library uses asyncio locks for race condition prevention. When testing:
_section_locks and _hot_water_group_locks dicts if needed# Locks are stored in these dictionaries:
bsblan._section_locks # {"heating": Lock(), "sensor": Lock(), ...}
bsblan._hot_water_group_locks # {"essential": Lock(), ...}
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer