How to mock authenticated users in tests
Simulate different user roles, scopes, superusers, and context restrictions without JWT overhead.
When writing integration tests, generating, signing, and refreshing JWT tokens introduces unnecessary test complexity. ZTestClient lets you mock user identities, scopes, and context attributes directly.
1. Mocking Scopes & Permissions
Pass user_id and the desired scopes list to simulate role-based authorization:
# test_permissions.py
import pytest
import uuid
from zcore.testing import ZTestClient
from main import app
@pytest.mark.asyncio
async def test_user_with_view_scope_only():
user_id = uuid.uuid4()
async with ZTestClient(
app=app,
user_id=user_id,
scopes=["tasks:view"] # User only has read permissions
) as client:
# 1. Allowed: GET /tasks
get_res = await client.get("/tasks")
assert get_res.status_code == 200
# 2. Forbidden: DELETE /tasks/{id} (Requires 'tasks:delete' scope)
del_res = await client.delete(f"/tasks/{uuid.uuid4()}")
assert del_res.status_code == 4032. Testing Superuser Privileges
Set is_superuser=True to test permission bypass mechanisms:
@pytest.mark.asyncio
async def test_superuser_bypasses_all_scopes():
async with ZTestClient(
app=app,
user_id=uuid.uuid4(),
scopes=[], # Zero scopes provided
is_superuser=True # Superuser flag bypasses checks
) as client:
res = await client.delete(f"/tasks/{uuid.uuid4()}")
assert res.status_code in [200, 404] # Did not get blocked by 403 Forbidden3. Custom Attributes and Context Restrictions
Use extra_user_attrs to attach domain-specific fields to the mock user, and extra_context to test Zchema field-masking:
@pytest.mark.asyncio
async def test_masked_fields_for_restricted_user():
async with ZTestClient(
app=app,
user_id=uuid.uuid4(),
scopes=["tasks:view"],
extra_user_attrs={"organization_id": uuid.uuid4(), "department": "Finance"},
extra_context={"restricted_fields": ["tasks.view.salary"]}
) as client:
res = await client.get("/tasks")
assert res.status_code == 200
# Assert that Zchema masked the salary field
for task in res.json()["data"]:
assert "salary" not in task4. Typed Pydantic User Models & Guest Requests
Instead of relying on a generic mock object, pass your authentic Pydantic user schema via user_model. This ensures endpoint dependencies receive a properly validated model instance:
from auth import AppUser
@pytest.mark.asyncio
async def test_with_typed_user_model():
user_id = uuid.uuid4()
async with ZTestClient(
app=app,
user_id=user_id,
user_model=AppUser,
extra_user_attrs={"department": "Engineering"}
) as client:
res = await client.get("/tasks")
assert res.status_code == 200To test public or guest-facing endpoints, simply omit user_id (or pass user_id=None). This exercises unauthenticated execution paths and validates optional authentication stubs (get_optional_user_stub).
Dual Context & Dependency Injection:
When user_id is supplied, ZTestClient simultaneously:
- Overrides
get_current_user_stubandget_optional_user_stub(along with any customuser_dependencytargets) with an authentic Pydantic model (ifuser_modelis provided) or a compliant mock object conforming toUserProtocol. - Binds
user_id,scopes, andextra_contextdirectly to the request-scopedZContext(ctx).