Test suite for the Synapse Python Client. Unit tests run without network access; integration tests hit the live Synapse API.
For new or significantly refactored tests, write async tests only and avoid adding new synchronous test modules. The @async_to_sync decorator is validated by a dedicated smoke test (tests/integration/synapseclient/models/synchronous/test_sync_wrapper_smoke.py). Legacy synchronous unit tests (under tests/unit/synapseclient/) still exist and are maintained, but should not be expanded.
Use pytest.mark.parametrize when possible to merge similar tests into one test.
pytest-socketblocks all network calls (unix sockets allowed on non-Windows for async event loop). On Windows, socket disabling is skipped entirely — tests still run but are not network-isolated.- Session-scoped
synfixture:Synapse(skip_checks=True, cache_client=False)with silent logger - Autouse
set_timezonefixture forcesTZ=UTCfor deterministic timestamps - Client caching disabled via
Synapse.allow_client_caching(False) - Use
AsyncMockfor async method mocking,create_autospecfor type-safe mocks - Class-based test organization with
@pytest.fixture(scope="function", autouse=True)for setup - Test file naming:
unit_test_*.py(legacy) ortest_*.py(newer) — both patterns are discovered by pytest - Mock isolation: when mocking fixture/instance attributes (e.g.,
self.syn.rest_post_async), always wrap inpatch.object()context managers instead of direct assignment. This prevents the mock from leaking to other tests:with patch.object(self.syn, "method_name", new_callable=AsyncMock, return_value=...):. Direct assignment leaves the mock in place after the test, polluting subsequent tests in the class.
- All async tests share one event loop:
asyncio_default_fixture_loop_scope = session schedule_for_cleanup(item)— defer entity/file cleanup to session teardown. Always use this instead of inline deletion. Cleanup list is reversed before execution for dependency ordering (children deleted before parents).- Use shared resources when possible via fixtures in
conftest.pyfiles (e.g.,project_model,project). Refer to existing integration tests for the pattern. - Per-worker project fixtures (
project_model,project) created during session setup --reruns 3for flaky retry,-n 8 --dist loadscopefor parallelism- OpenTelemetry tracing opt-in via
SYNAPSE_INTEGRATION_TEST_OTEL_ENABLEDenv var - Two client fixtures:
syn(silent logger) andsyn_with_logger(verbose) - conftest.py locations:
tests/unit/conftest.py(session client, socket blocking, UTC timezone),tests/integration/conftest.py(logged-in client, per-worker projects, cleanup fixture)
tests/test_utils.py:spy_for_async_function(original_func)— wraps async function for pytest-mock spying while preserving async behavior.spy_for_function(original_func)— sync variant.tests/integration/helpers.py:wait_for_condition(condition_fn, timeout_seconds=60)— async polling helper with exponential backoff. Accepts sync or async condition functions.tests/integration/__init__.py:QUERY_TIMEOUT_SEC = 600,ASYNC_JOB_TIMEOUT_SEC = 600- Test data generators in production code:
core/utils.pyhasmake_bogus_data_file(),make_bogus_binary_file(n),make_bogus_uuid_file()
asyncio_mode = auto in pytest.ini — all async test functions are auto-detected.
Sync wrapper smoke tests are skipped on Python 3.14+ — @async_to_sync raises RuntimeError when an event loop is already active (pytest-asyncio runs one). Users on 3.14+ must call async methods directly.
- Request fixtures explicitly in test function signatures — do not use
autouse=Truewithscope="function"to create Synapse resources, as this creates them for every test in the class even when not needed. - Use
autouse=Trueonly for side-effect fixtures (e.g., timezone setup) or at class/module scope when truly needed by all tests. - Prefer getting
synandschedule_for_cleanupdirectly from conftest fixtures instead of assigning them viaself.synin aninitfixture. - Scope resource fixtures carefully:
scope="function"ensures test isolation but increases API calls. Considerscope="module"orscope="class"for read-only resources, but be cautious — one test must not influence another. - Clean up in the fixture itself rather than having each test call
schedule_for_cleanupindividually:@pytest.fixture(scope="module") def project(syn, schedule_for_cleanup): project = Project(name=str(uuid.uuid4())).store(synapse_client=syn) schedule_for_cleanup(project) return project
- Unit tests must never make network calls —
pytest-socketwill fail them. Mock all HTTP interactions. - Integration test cleanup is mandatory — use
schedule_for_cleanup()for every created resource to avoid orphaned Synapse entities.