Testing¶
Backend tests are written with pytest and live in backend/tests/.
All tests mock the Supabase client to avoid hitting the real database.
Test Configuration¶
backend/pytest.ini:
[pytest]
pythonpath = .
testpaths = tests
python_files = test_*.py
asyncio_mode = strict
asyncio_default_fixture_loop_scope = function
asyncio_mode = strict— every async test must be marked with@pytest.mark.asyncioor use a fixture that handles the event loop.pythonpath = .— allowsfrom app.xxx import ...without installing the package.
Running Tests¶
cd backend
# All tests
pytest
# Single file
pytest tests/unit/test_auth_module.py
# Single test by name pattern
pytest -k "test_signup"
# Verbose output
pytest -v
# With coverage (requires pytest-cov)
pytest --cov=app --cov-report=html
Directory Structure¶
backend/tests/
├── conftest.py # Shared fixtures
├── unit/
│ └── test_auth_module.py # AuthService unit tests (8 tests)
└── integration/
└── test_auth.py # API endpoint integration tests (18 tests)
Fixtures (conftest.py)¶
A session-scoped autouse fixture patches the Supabase client with a
unittest.mock.MagicMock before every test, preventing real database calls.
@pytest.fixture(autouse=True)
def mock_supabase(mocker):
mocker.patch("app.core.database.supabase", new=MagicMock())
All tests inherit this fixture automatically — no explicit declaration needed.
Unit Tests (tests/unit/test_auth_module.py)¶
Unit tests call AuthService methods directly and assert on the return
values or raised exceptions. The Supabase client calls made inside the service
are all intercepted by the autouse mock.
Test |
What it verifies |
|---|---|
|
Successful signup returns a |
|
Supabase |
|
Valid credentials return a |
|
Wrong credentials raise |
|
Returns a dict containing a |
|
Valid refresh token returns a new |
|
Calls |
|
Returns a |
|
Updates |
Integration Tests (tests/integration/test_auth.py)¶
Integration tests use FastAPI’s TestClient and mock AuthService at the
router level, so the full HTTP request/response cycle is exercised without real
Supabase calls.
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
Test |
What it verifies |
|---|---|
|
|
|
Duplicate email returns |
|
|
|
Wrong password returns |
|
|
|
|
|
|
|
|
|
|
|
|
|
Missing header returns |
|
Invalid token returns |
|
|
|
Missing auth returns |
… (18 total) |
Writing New Tests¶
Unit test skeleton¶
import pytest
from unittest.mock import MagicMock
from app.modules.auth.service import AuthService, AuthServiceError
@pytest.mark.asyncio
async def test_my_service_method(mock_supabase):
# Arrange — configure the mock return value
mock_supabase.auth.some_method.return_value = MagicMock(
session=MagicMock(access_token="token123"),
user=MagicMock(id="uuid", email="a@b.com"),
)
# Act
result = await AuthService.some_method("arg")
# Assert
assert result.access_token == "token123"
Integration test skeleton¶
import pytest
from fastapi.testclient import TestClient
from unittest.mock import patch, MagicMock
from app.main import app
client = TestClient(app)
def test_my_endpoint(mocker):
mocker.patch(
"app.modules.auth.router.AuthService.some_method",
return_value=MagicMock(access_token="token123"),
)
response = client.post("/api/auth/some-endpoint", json={"key": "value"})
assert response.status_code == 200
assert response.json()["access_token"] == "token123"
Test Dependencies¶
Package |
Version |
Purpose |
|---|---|---|
|
9.0.2 |
Test runner |
|
1.3.0 |
Async test support ( |
|
3.15.1 |
|
|
(transitive) |
Required by FastAPI |