Skip to content

Factories

All factory functions listed here are exported from hassette.testing.

from hassette.testing 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.testing 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.testing 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, last_reported, context).

make_sensor_state_dict

make_sensor_state_dict builds a state dict for a sensor entity.

from hassette.testing 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.testing 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.testing 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.testing 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_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.testing 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 is not None
    assert config.token.get_secret_value() == "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"
web_api {"run": 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_history()
  • render_template()
  • ws_send_and_wait()
  • ws_send_json()
  • rest_request()
  • delete_entity()

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(), get_state_value(), get_attribute()) 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.testing import AppTestHarness

from my_apps.sync_app import SyncApp


async def test_sync_facade_recording():
    async with AppTestHarness(
        SyncApp, config={"instance_name": "sync_app"}
    ) 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