ha:testing
Python / Home Assistant testing patterns — pytest, the mealie-pattern conftest mock, JSON fixtures, snapshot_platform, black-box state-machine assertions. Use when working on tests/components/**/test_
SKILL.mdskills/testing/SKILL.md
GitHubHome Assistant Testing Reference
Entity-platform integrations: snapshot the whole platform with
snapshot_platformand set the entry up through the core API — assert what the user sees, not internal objects. See theentity-platformsskill.
Quick reference for Home Assistant (pytest) testing patterns.
Iron Laws — Never Violate These
- BLACK-BOX BY DEFAULT (P13) — Set the entry up via
hass.config_entries.async_setup; asserthass.states.get(...)andhass.services.async_call(...). Never touchruntime_data, coordinators, or entity objects — patch only the third-party library. "we should not touch runtime_data" — joostlek, https://github.com/home-assistant/core/pull/155099#discussion_r2488878373 - NO CONDITIONALS IN TESTS (P12) — No
if/branching in atest_*body; parametrize or split, and know the expected value in advance. "We should avoid if statements in tests." — edenhaus, https://github.com/home-assistant/core/pull/156588#discussion_r2707493472 - MOCK ONLY AT THE LIBRARY BOUNDARY (PS14) — One
autospec=Truelibrary mock inconftest.py, patched at BOTH the__init__andconfig_flowimport sites. Never patch core internals, the executor, or the stdlib. "we should make sure we patch the entrypoints" — joostlek, https://github.com/home-assistant/core/pull/175448#discussion_r3519164396 - FIXTURES ARE JSON (PS14) — Large/realistic payloads live in
tests/components/<domain>/fixtures/and load viaload_json_object_fixture; config entries reflect a real-world entry. "Consider storing the large amounts of data in json fixtures" — joostlek, https://github.com/home-assistant/core/pull/168125#discussion_r3082146845 - SIMULATE TIME (P14) — Advance the clock with
freezer.tick(...)+async_fire_time_changed(hass). Nevertime.sleep/asyncio.sleep(>0.1s), neverasync_refresh/async_update_entity. "use thefreezerto skip time... a 'natural' refresh" — joostlek, https://github.com/home-assistant/core/pull/162041#discussion_r2769309201 - CONFIG FLOW 100% COVERAGE (P15) — Every step and error path tested; each error path must recover and end in
CREATE_ENTRY. "This should end in CREATE_ENTRY to test that it's able to recover" — joostlek, https://github.com/home-assistant/core/pull/152000#discussion_r2649291946 - SNAPSHOT ENTITY PLATFORMS (PS13) — Use
snapshot_platform; review.ambrdiffs before commit; never re-assert what the snapshot already covers. "You probably can just usesnapshot_platform" — joostlek, https://github.com/home-assistant/core/pull/148989#discussion_r2227954744 - ASSERT CALL ARGS, NOT JUST COUNTS — Assert the arguments (and whole structures at once), not only that a mock was called. "Please assert the call args of all mock calls, besides the call count." — MartinHjelmare, https://github.com/home-assistant/core/pull/161936#discussion_r2848214581
Quick Decisions
Which Test Setup?
| Testing | Use |
|---|---|
| Config entry setup/reload/unload | MockConfigEntry + hass.config_entries.async_setup (test_init.py) — assert entry.state transitions (P13) |
| Config flow | hass.config_entries.flow.async_init(...) + async_configure(...) (test_config_flow.py, 100% — P15) |
| Entity platform | snapshot_platform + @pytest.mark.usefixtures("entity_registry_enabled_by_default") (PS13) |
| Service action | hass.services.async_call(...) and assert the state machine / mock call args (P18) |
Frontend src/common util |
Vitest unit test in the mirrored test/ path (FS15 / FI-6) |
When to worry about isolation?
- ✅ Black-box tests driven through the core config-entries API — survive refactors
- ✅ Time advanced with
freezer+async_fire_time_changed(P14) - ❌ Tests touching
runtime_data/coordinators/hass.datadirectly (P13) - ❌ Tests relying on real wall-clock time or
time.sleep(P14)
Mock or not?
- ✅ Mock: the third-party library, at its boundary, with
autospec=True(PS14) - ❌ Don't mock:
hass, the state machine, coordinators, your own flow code, or the stdlib
JSON fixture or inline dict?
- Use
load_json_object_fixturefor realistic / large API payloads - Inline a small dict only for a tiny one-off value the reader must see in the test
Quick Patterns
# Setup composition (black-box, P13)
async def test_setup(hass: HomeAssistant, mock_config_entry: MockConfigEntry) -> None:
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.LOADED
# State-machine assertion (never read entity objects)
assert hass.states.get("sensor.kitchen_temperature").state == "21.5"
# Service call + assert library call args (P18, MartinHjelmare)
await hass.services.async_call(DOMAIN, "set_mode", {"mode": "eco"}, blocking=True)
mock_client.set_mode.assert_awaited_once_with("eco")
# Simulate time — never sleep (P14)
freezer.tick(UPDATE_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
# Config flow recovery ends in CREATE_ENTRY (P15)
result = await hass.config_entries.flow.async_configure(result["flow_id"], user_input)
assert result["type"] is FlowResultType.CREATE_ENTRY
# Snapshot a whole platform (PS13)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
Common Anti-patterns
| Wrong | Right |
|---|---|
await asyncio.sleep(30) |
freezer.tick(...) + async_fire_time_changed(hass) (P14) |
coordinator.async_refresh() in a test |
Advance time for a natural refresh (P14) |
entry.runtime_data.client / hass.data[DOMAIN] in a test |
Assert via hass.states.get / service calls (P13) |
if condition: inside a test_* body |
@pytest.mark.parametrize the expected value (P12) |
| Large inline data dict in the test | JSON fixture + load_json_object_fixture (PS14) |
patch("...coordinator...") / patching core internals |
Patch the library at __init__ + config_flow sites (PS14) |
| Re-asserting each entity a snapshot covers | snapshot_platform once (PS13) |
References
For detailed patterns, see:
${CLAUDE_SKILL_DIR}/references/pytest-patterns.md- Fixtures, markers, parametrize, assertions, running subsets${CLAUDE_SKILL_DIR}/references/mocking-patterns.md- The mealie-pattern conftest, autospec, patch sites (PS14)${CLAUDE_SKILL_DIR}/references/lit-testing.md- ha-frontend Vitest tests forsrc/common, component testing (FS15/FI-6)${CLAUDE_SKILL_DIR}/references/fixture-patterns.md- JSON fixtures,load_json_object_fixture, service-action testing