smithery.ai

bsblan-testing

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.

First seen Apr 9, 2026

Installation

$ npx skills add https://smithery.ai

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 5,496 B
  • docs SUMMARY.md 184 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Testing python-bsblan

This skill guides you through testing practices for the python-bsblan library.

Test Structure

Tests are located in tests/ and use pytest with async support.

Basic Test Pattern

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

Using Fixtures

Test fixtures (JSON responses) are in tests/fixtures/. Common fixtures:

  • device.json - Device information
  • state.json - Current state
  • hotwaterstate.json - Hot water state
  • sensor.json - Sensor readings

Load 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]

Coverage Requirements

  • Total coverage: 95%+ required
  • Patch coverage: 100% required (all new/modified code must be fully tested)

Check Coverage

# 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

Verify New Code is Covered

After adding new methods, always verify coverage:

  1. Run tests with --cov-report=term-missing
  2. Check the "Missing" column shows no line numbers for your new code
  3. Look for uncovered branches (shown as line->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).

GitHub Actions Coverage

CI enforces:

  • Total coverage ≥ 95%
  • Patch coverage = 100% (Codecov checks new/modified lines)

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.

Running Tests

# 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

Prek Hooks

Always run before committing:

uv run prek run --all-files

This runs:

  • Ruff: Linting and formatting (88 char line limit)
  • MyPy: Static type checking
  • Pylint: Code analysis

Mock Patterns

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"},
)

Fixture Setup and Registration

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.

Mocking HTTP Response Handling

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

Testing Lazy Loading

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", ...})

Testing Concurrent Access

The library uses asyncio locks for race condition prevention. When testing:

  • Locks are created per-section/group automatically
  • Access sectionlocks and hotwatergrouplocks dicts if needed
  • The double-checked locking pattern prevents duplicate validations
# Locks are stored in these dictionaries:
bsblan._section_locks  # {"heating": Lock(), "sensor": Lock(), ...}
bsblan._hot_water_group_locks  # {"essential": Lock(), ...}