Factories
All factory functions listed here are exported from hassette.test_utils.
from hassette.test_utils import (
create_call_service_event,
create_state_change_event,
make_light_state_dict,
make_sensor_state_dict,
make_state_dict,
make_switch_state_dict,
)
State Factories
State factories build raw HA-format state dicts. The harness calls them internally for set_state(). Tests that need precise attribute control call them directly.
make_state_dict
make_state_dict builds a minimal state dict in Home Assistant wire format.
from hassette.test_utils import make_state_dict
state = make_state_dict(
"sensor.temperature",
"21.5",
attributes={"unit_of_measurement": "°C", "device_class": "temperature"},
)
| Parameter | Default | Description |
|---|---|---|
entity_id |
required | Entity ID, e.g. "sensor.temperature". |
state |
required | State string, e.g. "on", "25.5". |
attributes |
None |
Attributes dict. Defaults to {}. |
last_changed |
None |
ISO timestamp string. Defaults to now. |
last_updated |
None |
ISO timestamp string. Defaults to now. |
context |
None |
Context dict. Defaults to a generated UUID context. |
make_light_state_dict
make_light_state_dict builds a state dict for a light entity with brightness and color_temp support.
from hassette.test_utils import make_light_state_dict
state = make_light_state_dict(
entity_id="light.kitchen",
state="on",
brightness=200,
color_temp=370,
)
| Parameter | Default | Description |
|---|---|---|
entity_id |
"light.kitchen" |
Light entity ID. |
state |
"on" |
"on" or "off". |
brightness |
None |
Brightness 0–255. Omitted from attributes if not set. |
color_temp |
None |
Color temperature in mireds. Omitted from attributes if not set. |
**kwargs |
Extra attributes or top-level state dict fields (last_changed, last_updated, context). |
make_sensor_state_dict
make_sensor_state_dict builds a state dict for a sensor entity.
from hassette.test_utils import make_sensor_state_dict
state = make_sensor_state_dict(
entity_id="sensor.temperature",
state="21.5",
unit_of_measurement="°C",
device_class="temperature",
)
| Parameter | Default | Description |
|---|---|---|
entity_id |
"sensor.temperature" |
Sensor entity ID. |
state |
"25.5" |
Sensor value as a string. |
unit_of_measurement |
None |
Unit string, e.g. "°C", "%". Omitted from attributes if not set. |
device_class |
None |
HA device class, e.g. "temperature", "humidity". Omitted from attributes if not set. |
**kwargs |
Extra attributes or top-level state dict fields. |
make_switch_state_dict
make_switch_state_dict builds a state dict for a switch entity.
from hassette.test_utils import make_switch_state_dict
state = make_switch_state_dict(entity_id="switch.outlet", state="off")
| Parameter | Default | Description |
|---|---|---|
entity_id |
"switch.outlet" |
Switch entity ID. |
state |
"on" |
"on" or "off". |
**kwargs |
Extra attributes or top-level state dict fields. |
Event Factories
Event factories build typed event objects for direct bus dispatch. Most tests call harness.simulate_state_change() or harness.simulate_call_service() instead. These factories cover tests that bypass simulate_* and exercise lower-level bus methods.
create_state_change_event
create_state_change_event builds a RawStateChangeEvent suitable for direct bus dispatch.
from hassette.test_utils import create_state_change_event
event = create_state_change_event(
entity_id="binary_sensor.motion",
old_value="off",
new_value="on",
old_attrs={"device_class": "motion"},
new_attrs={"device_class": "motion"},
)
| Parameter | Default | Description |
|---|---|---|
entity_id |
required | Entity ID. |
old_value |
required | Old state value. None simulates entity creation. |
new_value |
required | New state value. None simulates entity removal. |
old_attrs |
None |
Attributes for the old state dict. |
new_attrs |
None |
Attributes for the new state dict. |
When old_value or new_value is None, the corresponding state dict is None in the event, not {"state": None, ...}. This matches HA's wire format for entity creation and removal.
create_call_service_event
create_call_service_event builds a CallServiceEvent.
from hassette.test_utils import create_call_service_event
event = create_call_service_event(
domain="light",
service="turn_on",
service_data={"entity_id": "light.kitchen", "brightness": 200},
)
| Parameter | Default | Description |
|---|---|---|
domain |
required | Service domain, e.g. "light". |
service |
required | Service name, e.g. "turn_on". |
service_data |
None |
Service data dict. Defaults to {}. |
make_mock_hassette
make_mock_hassette returns a sealed AsyncMock with a real, Pydantic-validated HassetteConfig. It wires readiness events, scheduler service stubs, bus service stubs, and other standard attributes without running Hassette.__init__.
from pathlib import Path
from hassette.test_utils import make_mock_hassette
tmp_path = Path("/tmp/test")
# Minimal — real config defaults, sealed against phantom attributes
hassette = make_mock_hassette()
# With config overrides — validated by HassetteConfig at construction time
hassette = make_mock_hassette(strict_lifecycle=True)
hassette = make_mock_hassette(database={"retention_days": 14})
# Database-backed tests — pass a real tmp_path for isolation
hassette = make_mock_hassette(data_dir=tmp_path)
HassetteConfig validates config overrides at construction time. An unrecognized field name or out-of-range value raises pydantic.ValidationError immediately. Nested group fields accept dicts or model instances.
The mock is sealed by default. Accessing any attribute not wired by the factory raises AttributeError. sealed=False allows adding extra attributes after construction.
| Parameter | Default | Description |
|---|---|---|
data_dir |
tempfile.mkdtemp() |
Directory for Hassette data files. Tests needing DB isolation typically pass tmp_path. |
set_ready |
True |
Pre-sets ready_event so wait_for_ready() resolves immediately. |
set_loop |
True |
Sets loop to asyncio.get_running_loop(). False suits session-scoped fixtures running outside an event loop. |
sealed |
True |
Calls seal() after wiring. Unlisted attribute access raises AttributeError. |
**config_overrides |
Any HassetteConfig field, merged on top of test defaults. |
make_test_config
make_test_config builds a HassetteConfig without a TOML file, env file, or CLI args. Only the values passed are read. Pydantic validation still runs.
from pathlib import Path
from hassette.test_utils import make_test_config
def test_config_defaults(tmp_path: Path):
config = make_test_config(data_dir=tmp_path)
assert config.web_api.run is False
# Override specific fields
config = make_test_config(data_dir=tmp_path, base_url="http://192.168.1.100:8123")
assert config.base_url == "http://192.168.1.100:8123"
def test_config_overrides(tmp_path: Path):
config = make_test_config(data_dir=tmp_path, token="my-real-token", web_api={"run": True})
assert config.token == "my-real-token"
assert config.web_api.run is True
AppTestHarness creates a config internally. make_test_config covers tests that need a HassetteConfig directly without the full harness, such as config parsing or validation logic.
data_dir is required. All other fields have test-appropriate defaults:
| Field | Default |
|---|---|
data_dir |
required (no default) |
token |
"test-token" |
base_url |
"http://test.invalid:8123" |
disable_state_proxy_polling |
True |
apps |
{"autodetect": False} |
web_api |
{"run": False} |
run_app_precheck |
False |
**overrides replace any of the defaults.
RecordingApi Coverage Boundary
RecordingApi records write-method calls and delegates read methods to the seeded StateProxy. Methods requiring a live HA connection raise NotImplementedError.
Explicit stubs that raise NotImplementedError directly:
get_state_raw()get_states_raw()get_history()render_template()ws_send_and_wait()ws_send_json()rest_request()delete_entity()
Redirected via __getattr__ with a message pointing to get_state():
get_state_value()get_state_value_typed()get_attribute()
Any other public name not defined on RecordingApi also falls through to __getattr__ and raises NotImplementedError.
harness.set_state() seeds data for read methods. Read methods that delegate to StateProxy (get_state(), get_states(), get_entity(), get_entity_or_none(), entity_exists(), get_state_or_none()) return seeded values directly.
harness.api_recorder.sync is a RecordingSyncFacade. Write calls made via self.api.sync.* appear in the same api_recorder.calls list as their async counterparts. The same assertion API works for both:
from hassette.test_utils import AppTestHarness
from my_apps.sync_app import SyncApp
async def test_sync_facade_recording():
async with AppTestHarness(SyncApp, config={}) as harness:
await harness.simulate_state_change("binary_sensor.motion", old_value="off", new_value="on")
# Your app calls: self.api.sync.turn_on("light.kitchen", domain="light")
harness.api_recorder.assert_called("turn_on", entity_id="light.kitchen", domain="light")
Methods not covered by the sync facade raise NotImplementedError rather than silently succeeding.
Next Steps
- Testing overview: harness basics and test patterns
- Time Control: freeze and advance time for scheduler tests
- Concurrency & pytest-xdist: concurrency locks and xdist isolation