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.asyncio or use a fixture that handles the event loop.

  • pythonpath = . — allows from 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

test_signup_success

Successful signup returns a TokenResponse with an access token.

test_signup_failure

Supabase AuthApiError is re-raised as AuthServiceError.

test_login_success

Valid credentials return a TokenResponse.

test_login_failure

Wrong credentials raise AuthServiceError with status 401.

test_google_oauth_url

Returns a dict containing a url key.

test_refresh_session

Valid refresh token returns a new TokenResponse.

test_logout

Calls supabase.auth.sign_out() and returns a success message.

test_get_user_from_token

Returns a UserResponse for a valid access token.

test_update_onboarding

Updates profiles row and returns a success message.

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

test_signup_success

POST /api/auth/signup returns 201 and a token response.

test_signup_duplicate

Duplicate email returns 400.

test_login_success

POST /api/auth/login returns 200 and a token response.

test_login_wrong_password

Wrong password returns 400.

test_google_oauth_url

POST /api/auth/google/url returns 200 with url field.

test_google_callback

POST /api/auth/google/callback with valid code returns 200.

test_google_token

POST /api/auth/google/token with valid id_token returns 200.

test_refresh_token

POST /api/auth/refresh with valid refresh token returns 200.

test_logout

POST /api/auth/logout with valid auth header returns 200.

test_get_current_user

GET /api/auth/me returns 200 and user data.

test_get_current_user_no_auth

Missing header returns 403.

test_get_current_user_invalid_token

Invalid token returns 401.

test_update_onboarding

PUT /api/auth/onboarding with valid body returns 200.

test_update_onboarding_no_auth

Missing auth returns 403.

… (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

pytest

9.0.2

Test runner

pytest-asyncio

1.3.0

Async test support (asyncio_mode = strict)

pytest-mock

3.15.1

mocker fixture for unittest.mock integration

httpx

(transitive)

Required by FastAPI TestClient