The NetCommander project includes comprehensive test coverage:
- Unit Tests: Test individual components in isolation
- Integration Tests: Test with real hardware (optional)
- Test Coverage: Measure code coverage and identify gaps
tests/
├── conftest.py # Shared fixtures and mocks
├── test_client.py # API client unit tests
├── test_models.py # Data model tests
├── test_ha_coordinator.py # Home Assistant coordinator tests
├── test_ha_config_flow.py # Config flow tests
└── test_integration.py # Integration tests (require device)
# Using uv
uv pip install -e ".[dev,cli,ha]"
# Or using make
make install-dev# Using pytest directly
pytest -m "not integration"
# Or using make
make test# Test API client only
pytest tests/test_client.py -v
# Test models only
pytest tests/test_models.py -v
# Test Home Assistant components
pytest tests/test_ha_coordinator.py tests/test_ha_config_flow.py -v# Generate coverage report
make test-cov
# View HTML report
open htmlcov/index.htmlUnit tests mock all external dependencies and test components in isolation.
Tests the NetCommanderClient class:
- Client initialization
- Context manager usage
- Status retrieval and parsing
- Device info retrieval and parsing
- Outlet control (on/off/toggle)
- Batch operations (all on/off)
- Error handling (authentication, connection, commands)
- Invalid input validation
Example:
pytest tests/test_client.py::TestNetCommanderClient::test_get_status_success -vTests Pydantic data models:
- DeviceStatus creation and properties
- DeviceInfo creation with optional fields
- OutletState enum values
- Property methods (outlets_on, outlets_off)
Example:
pytest tests/test_models.py::TestDeviceStatus -vTests the Home Assistant data coordinator:
- Coordinator initialization
- Data update cycle
- Device info caching
- Outlet control methods
- Reboot functionality
- Error handling and UpdateFailed
Example:
pytest tests/test_ha_coordinator.py -vTests the Home Assistant config flow:
- Form display
- Successful configuration
- Connection error handling
- Authentication error handling
- Input validation
- Unique ID generation (MAC or IP fallback)
Example:
pytest tests/test_ha_config_flow.py -vIntegration tests connect to a real device and verify end-to-end functionality.
- Physical Device: NetCommander PDU on your network
- Environment Variables:
export NETCOMMANDER_HOST=192.168.1.100 export NETCOMMANDER_PASSWORD=admin export NETCOMMANDER_USER=admin # optional, defaults to 'admin' export RUN_INTEGRATION_TESTS=1
# Using make (recommended)
make test-integration
# Using pytest directly
RUN_INTEGRATION_TESTS=1 pytest tests/test_integration.py -v -s
# Run specific integration test
RUN_INTEGRATION_TESTS=1 pytest tests/test_integration.py::TestIntegration::test_get_status -v -s- Device Info: Fetch real device information
- Status Retrieval: Get current outlet states
- Outlet Control: Turn outlets on/off and verify
- Toggle: Test toggle functionality
- Current Monitoring: Monitor current draw changes
- Error Handling: Invalid outlet numbers
- Session Management: Multiple calls with same session
Note: Integration tests modify outlet states but restore them afterward.
# HTML report (recommended)
make test-cov
open htmlcov/index.html
# Terminal report
pytest --cov=src/netcommander --cov=custom_components/netcommander --cov-report=term-missing- API Client: >90% coverage
- Models: 100% coverage
- Coordinator: >85% coverage
- Config Flow: >80% coverage
pytest --cov=src/netcommander --cov-report=term-missingLocated in tests/conftest.py:
device_info: Sample DeviceInfo objectdevice_status: Sample DeviceStatus objectmock_client: Mocked NetCommanderClientmock_response: Mocked aiohttp responsemock_session: Mocked aiohttp sessionconnection_params: Standard connection parametershass: Mock Home Assistant instance (HA tests)
def test_example(device_status, mock_client):
"""Test using fixtures."""
assert device_status.outlets[1] is True
assert mock_client.host == "192.168.1.100"name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install uv
uv pip install -e ".[dev,cli,ha]"
- name: Run tests
run: make test
- name: Upload coverage
uses: codecov/codecov-action@v3pytest -v -s tests/test_client.pypytest tests/test_client.py::TestNetCommanderClient::test_get_status_success -vpytest --pdb tests/test_client.py# -s flag shows print output
pytest -s tests/test_integration.pyProblem: ModuleNotFoundError: No module named 'netcommander'
Solution: Ensure you've installed the package in editable mode:
uv pip install -e .Problem: RuntimeWarning: coroutine was never awaited
Solution: Ensure async tests use @pytest.mark.asyncio decorator:
@pytest.mark.asyncio
async def test_async_function():
result = await some_async_function()
assert result is TrueProblem: Integration tests are skipped
Solution: Set the required environment variable:
export RUN_INTEGRATION_TESTS=1"""Tests for new feature."""
import pytest
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from netcommander import NetCommanderClient
class TestNewFeature:
"""Test new feature."""
@pytest.mark.asyncio
async def test_feature_success(self, mock_client):
"""Test successful feature operation."""
# Arrange
mock_client.new_method = AsyncMock(return_value=True)
# Act
result = await mock_client.new_method()
# Assert
assert result is True
mock_client.new_method.assert_called_once()@pytest.mark.asyncio
async def test_new_feature_integration(self, integration_config):
"""Test new feature with real device."""
async with NetCommanderClient(**integration_config) as client:
result = await client.new_method()
assert result is not None- Test Names: Use descriptive names that explain what is being tested
- Arrange-Act-Assert: Structure tests clearly
- One Assertion: Focus each test on one behavior
- Mock External Calls: Don't rely on network in unit tests
- Clean Up: Restore state in integration tests
- Document: Add docstrings explaining test purpose
make test # Run unit tests only
make test-unit # Run specific unit tests
make test-integration # Run integration tests (requires device)
make test-cov # Run with coverage report
make lint # Check code style
make format # Auto-format code
make type-check # Run type checking
make clean # Clean test artifactsAfter running tests, you'll find:
.coverage: Coverage data filehtmlcov/: HTML coverage report.pytest_cache/: Pytest cache