test: harden test suite — real objects over mocks, integration as pytest#88
Conversation
- test_exporter.py: add _Stub class; replace _make_mfr/_make_dt/_make_mt/_make_rt
MagicMock() with _Stub(**kwargs) so absent-field access raises AttributeError
instead of silently returning a truthy sub-mock
- test_nb_dt_import.py: upgrade bare assert_called_once() to also assert first
positional arg and key kwargs on create_device_types, create_module_types,
create_rack_types, and the three _process_*_types patched in the vendor loop test
- test_change_detector.py: replace MagicMock() for netbox_dt/existing_comp/
netbox_comp/existing with DotDict({...}) — the real type the change detector
receives from GraphQL; absent fields now raise AttributeError instead of
returning a truthy sub-mock, so a new comparison field added to
DEVICE_TYPE_PROPERTIES actually fails rather than silently passing
- test_repo.py: replace patch('os.listdir') / patch('os.path.exists') /
patch('os.path.isdir') in TestGetDevices and TestDiscoverVendors with real
tmp_path directories; catch path-construction bugs bypassed by the old mocks;
keep OS-error test mock-based (simulating permission errors is not portable)
Restore test_default_mode_no_device_types, whose `def` line was accidentally deleted in e57b3e7 — its body had been folded into the preceding test (count 73 -> 74). Replace data-stub MagicMocks with real objects so absent-field branches are exercised faithfully (a MagicMock fabricates any attribute, masking them): - test_change_detector: TestCompareImageProperties netbox_dt -> DotDict - test_normalization: choice records -> SimpleNamespace (hasattr(.value) was always True under MagicMock) - test_log_handler: port records -> SimpleNamespace (exercises the hasattr(port,'type') else-branch) - test_update_failure_resolver: template/device records -> SimpleNamespace Boundary mocks kept: pynetbox client, HTTP responses, Rich console, loggers, and the template asserting .delete(). Polish: drop the redundant main() runner from the integration test; document that the skip guard sees .env values via load_dotenv; fix stale sys.exit/usage references in the integration docstring.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (13)
WalkthroughThe PR migrates the integration test suite from a standalone Python script to pytest by registering an ChangesIntegration test suite migration to pytest
Unit test mock cleanup and assertion strengthening
Sequence Diagram(s)sequenceDiagram
participant CI as GitHub Actions
participant pytest as pytest runner
participant int_conftest as integration/conftest.py
participant root_conftest as tests/conftest.py
participant test as test_import.py tests
CI->>pytest: uv run pytest tests/integration/ -m integration -x -v --timeout=600
pytest->>int_conftest: pytest_collection_modifyitems(items)
int_conftest->>int_conftest: attach integration mark to path-matched items
int_conftest->>int_conftest: read NETBOX_URL, NETBOX_TOKEN from os.environ
alt env vars missing
int_conftest->>test: attach pytest.mark.skip
end
pytest->>root_conftest: mock_env_vars(request), mock_git_repo(request), mock_graphql_requests(request)
root_conftest->>root_conftest: check request.node integration mark
alt integration mark present
root_conftest-->>test: yield (no patching)
else unit test
root_conftest->>root_conftest: patch env vars / git repo / requests.Session
root_conftest-->>test: yield mocked dependencies
end
pytest->>test: run test functions via pytest.fail() on failure
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Overview
Hardens the test suite along two axes:
MagicMocks with real objects so absent-field branches are exercised faithfully. AMagicMocksynthesizes any attribute on demand, so a mock-heavy test stays green even when the production path reads a field that was never set.pytest.failinstead ofsys.exit, anintegrationmarker, a collection-time skip guard, and CI invoking it viapytestrather thanpython.Changes
integrationmarker; module-levelpytestmark; collection-time skip guard that marks the whole package skipped whenNETBOX_URL/NETBOX_TOKENare absent (so it showss, not errors); autouse mock fixtures bail out for integration-marked tests; CI runspytest tests/integration/ -m integration -x -v --timeout=600.test_change_detector/test_exporter/test_repo/test_nb_dt_import:DotDict(the production GraphQL-record wrapper), a strict_Stub, and realtmp_pathdirectories instead ofos.listdir/exists/isdirpatches.test_normalization: choice records →SimpleNamespace—hasattr(.value)was always True underMagicMock.test_log_handler: port records →SimpleNamespace, now exercising thehasattr(port,'type')else-branch.test_update_failure_resolver: template/device records →SimpleNamespace..delete().test_default_mode_no_device_types, whosedefline had been accidentally deleted (its body was folded into the preceding test; count 73 → 74).main()runner from the integration test (its hardcoded test list could silently drift)..envvalues viaload_dotenv; fix stalesys.exit/usage references in the integration docstring.Verification
uv run pytest tests/→ 939 passeduvx ruff check tests/→ cleanSummary by CodeRabbit
Release Notes