docs(tests): add guidelines for stubbing modules in standalone-capable tests

This commit is contained in:
Jokob @NetAlertX committed 2026-08-21 22:44:08 +00:00
1 parent 8000d9b453
commit 7cbdeb9855
2 files changed
+55

No files matched your search

+9
View File
@@ -98,6 +98,15 @@ from db_test_helpers import make_db, DummyDB, insert_device, minutes_ago
If a helper you need doesn't exist yet, add it to `db_test_helpers.py` — not locally in the test file.
## Stubbing Modules in Standalone-Capable Tests
If a test stubs NetAlertX modules into `sys.modules` so a script can be imported
outside the container (see `test/plugins/test_ntfy_custom_headers.py`), pop each
stubbed name back out of `sys.modules` right after the one-time import that needed
it. Otherwise the fake module leaks into every other test file collected in the
same pytest session and shadows the real module (see `testing-workflow` skill for
the full pattern and reproduction steps).
## MAC Literals in Tests — ALWAYS Lowercase
**MANDATORY:** Every MAC address literal used in test fixtures, parametrize decorators, assertions, or comments must be lowercase hex:
+46
View File
@@ -59,3 +59,49 @@ docker buildx build -t netalertx-test .
```
This takes ~30 seconds unless venv stage changes (~90s).
## Pitfall: `sys.modules` Stubbing Leaks Across Test Files
Some plugin tests (e.g. `test/plugins/test_ntfy_custom_headers.py`) stub NetAlertX
modules (`conf`, `helper`, `models.notification_instance`, etc.) via
`sys.modules[name] = fake_module` so the plugin script can be imported standalone,
outside the container. Because `sys.modules` is a single process-wide cache shared
by the whole pytest session, a fake module inserted by one test file silently
shadows the real module for every other test file collected afterwards — pytest
imports all test files during collection, before any test runs, so this can happen
regardless of alphabetical/directory order.
Symptom: `AttributeError: <module 'models.notification_instance'> does not have
the attribute 'get_setting_value'` (or similar) in an unrelated test file, where
the module repr has no `from '<path>'` suffix — a giveaway that a stub, not the
real module, was resolved.
Fix pattern: track which module names your stub actually inserted, and pop them
back out of `sys.modules` immediately after the one-time import that needed them
(the already-imported script keeps its bound names regardless):
```python
_stubbed_module_names = []
def _stub(name, **attrs):
if name not in sys.modules:
mod = types.ModuleType(name)
for k, v in attrs.items():
setattr(mod, k, v)
sys.modules[name] = mod
_stubbed_module_names.append(name)
# ... _stub(...) calls, then the one-time import ...
import ntfy
for _name in _stubbed_module_names:
sys.modules.pop(_name, None)
```
Reproduce cross-file pollution locally by running the suspect file together with
the affected one in a single pytest invocation (order matters less than you'd
think — collection happens for all files first):
```bash
pytest test/plugins/test_ntfy_custom_headers.py test/backend/test_notification_templates.py -v
```