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.mjsfiles (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_loudlycan fail withAssertionError: assert 'not identified as any supported brand' in ''when the suite is run as exactlytests/unit tests/integration tests/adapters(the path setscripts/update_test_docs.pyuses) โ thecaplogfixture sees no records even though the code under test does log. It passes standalone and under the CI gate (pytest tests, which also collectstests/replay). Reproduced twice, with and without--cov. This reads as a cross-test logger-state leak:debug_capture.py'sDebugCapture.start()/.stop()save/restore thecustom_components.eufy_vacuumpackage logger'spropagateflag (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'scaplogrecords. 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-runagainst acoverage.jsonproduced by a manualpytest tests/unit tests/integration tests/adapters --cov ...(accepting the same one failure) or by wideningTEST_PATHStotests(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 coverstests/unit+tests/integration; passteststo also gatetests/adaptersandtests/replaythe way CI does. - Never run pytest directly on Windows โ
pytest-homeassistant-custom-componentimportsfcntl, 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 withnpm run test:harness(frontend/render-harness, Linux-only baselines, pinned Playwright image). Neither is pytest. - New integration test โ use the
manager_with_servicesfixture and the seeding helpers intests/integration/conftest.py. Start from a template in 06. - Managed rooms live at
data["maps"][vac][map]["rooms"], notdata["rooms"]. This one mistake invalidates more tests than any other โ see 05.