Testing
Stack: pytest, pytest-asyncio, python-dotenv. Setup lives in tests/conftest.py.
Rules
- Mark async tests with
@pytest.mark.asyncio(or set the asyncio mode in conftest if it's already configured — check there first). - Async fixtures:
@pytest.fixture+async defreturning the value, or@pytest_asyncio.fixturefor explicit async fixtures. - Don't hit real providers in CI. Mock provider SDKs (
unittest.mock.patch, or pass an injected client to the constructor). .envloaded inconftest.py; tests assume env is available, but secrets should not be required for unit tests — use mocks.- Test the agentic loop end-to-end when touching providers —
invoke()with a tool that returns deterministically, assert the final context has the expectedToolCall+ToolResult+ assistant message. - Test schemas with
model_validate, not by constructing fields manually — catches Pydantic v2 regressions. - Async resource cleanup in tests: use
async withortry/finallyso a failing test doesn't leak subprocesses (MCP stdio especially).
Existing patterns
tests/test_llm_response.py— provider response shape.tests/test_prompts.py— prompt provider behavior.tests/test_postgres_binder.py— binder type mapping.
Common mistakes
- Forgetting
@pytest.mark.asyncio→ test silently doesn't await. - Hitting
bedrock.client(...)in CI → mock it. - Leaving MCP stdio managers open after a test → flakes from subprocess leaks.
- Asserting on full LLM response strings → brittle; assert on structure (tool calls, message roles).
See also
[[python-async-discipline]],[[echo-sdk-llm]]
Source: eka-care/echo-sdk — distributed by TomeVault.