Skip to content

Testing Docs โ€” Reading Order

How the test suite is built, how to run it, and how to add to it without rebuilding the scaffolding every time.

The suite currently has 3,912 test functions across 212 test files (86 unit, 108 integration, 18 adapter) โ€” 4,602 cases after parametrization โ€” running on Python 3.14 inside a Linux container. The CI behavior gate (pytest tests --no-cov, which also collects the fourth tests/replay/ directory below โ€” 3,869 cases total) is all green. Prefer that number and that command when you need "is the suite green": running the doc-tool's narrower tests/unit tests/integration tests/adapters path set on its own has been observed to fail one adapter test (tests/adapters/test_brand_selection.py::test_register_brand_adapter_refuses_loudly, passes in isolation and under the full tests gate โ€” see bug signals) via an apparent cross-test state leak, not a real regression. Those 3,369/3,859 exercise the 225 source modules under custom_components/eufy_vacuum/ to 94.1% coverage (92% combined with branch coverage, adapters included); see the subsystems index for the per-subsystem breakdown. A separate fourth track, the recorder-replay harness (tests/replay/ โ€” real recorded device runs fired through the production listener layer), is described in 01 โ€” overview.


Start here

# File What it covers
01 overview Test philosophy, the three layers (unit / integration / adapter), directory layout, coverage status
02 running-tests scripts\test.bat, why tests must run in Docker, running subsets, reading coverage

Reference

# File What it covers
03 fixtures-and-helpers Every fixture (hass, manager, manager_with_services, config entries) and the seeding helpers
04 patterns-and-conventions Coverage-target IDs, file/test naming, calling services, sync-via-executor, the unit-mock pattern
05 gotchas-and-pitfalls The traps that cost the most time: shared config_dir, the real data layout, learning blockers, adapter registry wiring

Do the thing

# File What it covers
06 recipes Copy-paste templates: a service test, an entity test, a unit test, a finalize test, an adapter-config test

Frontend (JS) โ€” its own set

The card is a separate JS track (not part of the Python count above), documented as its own set under frontend/:

  • frontend/unit-tests โ€” the pure-JS logic units: 904 cases across 91 src/**/*.test.mjs files (coordinate math, validation engines, state accessors, theme/colour resolution, and the audit campaign's regression files); npm run test:units.
  • frontend/render-harness โ€” the headless render-harness gates (smoke, visual regression, CVD, shape marks, intake) and the Docker baseline workflow; npm run test:harness.

Subsystem test maps

Per-subsystem "what's tested and how" โ€” start from the learning map (the template).

All 18 subsystems are mapped (core + every package + the HA-facing layers), numbered by the start pipeline then peripherals โ€” see the subsystems index for the full table and per-subsystem coverage. Highlights:

Doc What it covers
subsystems/ Index of all per-subsystem test maps + coverage conventions
subsystems/01-core The orchestrator โ€” lifecycle, job progress, start-status, delegation seams, errors, storage
subsystems/06-learning The learning subsystem โ€” coverage map, behaviors, setup patterns, gaps (detailed template)
subsystems/10-dock The dock subsystem โ€” action gating, dispatch, event recording (compact template)

Known test-suite issues

  • tests/adapters/test_brand_selection.py::test_register_brand_adapter_refuses_loudly can fail with AssertionError: assert 'not identified as any supported brand' in '' when the suite is run as exactly tests/unit tests/integration tests/adapters (the path set scripts/update_test_docs.py uses) โ€” the caplog fixture sees no records even though the code under test does log. It passes standalone and under the CI gate (pytest tests, which also collects tests/replay). Reproduced twice, with and without --cov. This reads as a cross-test logger-state leak: debug_capture.py's DebugCapture.start()/.stop() save/restore the custom_components.eufy_vacuum package logger's propagate flag (tests/unit/test_debug_capture.py), and that is the only other place in the suite that mutates it โ€” a run ordering where a capture is left active (or restored to the wrong prior value) would silently swallow this test's caplog records. Not confirmed as the exact mechanism; flagged as a bug signal per 00 ยง3 rather than patched here. Consequence: scripts/update_test_docs.py's own coverage run (subprocess.run(..., check=True)) raises on this path set, so a doc regen currently needs --no-run against a coverage.json produced by a manual pytest tests/unit tests/integration tests/adapters --cov ... (accepting the same one failure) or by widening TEST_PATHS to tests (not done here โ€” out of this doc's scope).

TL;DR

  • Run everything: scripts\test.bat (from a Windows shell; it spins up the container for you). The default run covers tests/unit + tests/integration; pass tests to also gate tests/adapters and tests/replay the way CI does.
  • Never run pytest directly on Windows โ€” pytest-homeassistant-custom-component imports fcntl, which does not exist on Windows. See 02.
  • Frontend / card tests are separate โ€” pure-JS logic units run with npm run test:units (frontend/unit-tests); the rendered card + visual baselines run with npm run test:harness (frontend/render-harness, Linux-only baselines, pinned Playwright image). Neither is pytest.
  • New integration test โ†’ use the manager_with_services fixture and the seeding helpers in tests/integration/conftest.py. Start from a template in 06.
  • Managed rooms live at data["maps"][vac][map]["rooms"], not data["rooms"]. This one mistake invalidates more tests than any other โ€” see 05.