Testing Guide

Flask-More-Smorest provides testing helpers to simplify testing authenticated endpoints and permission-based views.

Testing Helpers

The flask_more_smorest.testing module provides context managers and utility functions to simplify testing with JWT authentication.

Context Managers

as_user(client, user_id, additional_claims=None)

Context manager to set JWT authentication for a user in test requests.

as_admin(client, user_id, additional_claims=None, roles=None)

Context manager to set JWT authentication for an admin user in test requests.

Utility Functions

clear_registration()

Clear the custom user registration, resetting to default JWT behavior.

Quick Start

Testing authenticated endpoints:

import pytest
from flask_more_smorest.perms.models.defaults import User
from flask_more_smorest.testing import as_user

def test_get_my_profile(client, db_session):
    # Create test user
    with User.bypass_perms():
        user = User(email="test@example.com", password="password123")
        user.save()

    # Test authenticated endpoint
    with as_user(client, str(user.id)):
        response = client.get("/api/users/me/")
        assert response.status_code == 200
        assert response.json["email"] == "test@example.com"

Testing admin-only endpoints:

import pytest
from flask_more_smorest.perms.models.defaults import User, UserRole, BaseRoleEnum
from flask_more_smorest.testing import as_admin

def test_admin_endpoint(client, db_session):
    # Create admin user
    with User.bypass_perms():
        admin = User(email="admin@example.com", password="password123")
        admin.save()
        admin.roles.append(UserRole(user=admin, role=BaseRoleEnum.ADMIN))

    # Test admin-only endpoint
    with as_admin(client, str(admin.id)):
        response = client.get("/api/users/")
        assert response.status_code == 200

Testing superadmin endpoints:

def test_superadmin_endpoint(client, db_session):
    with User.bypass_perms():
        admin = User(email="superadmin@example.com", password="password123")
        admin.save()
        admin.roles.append(UserRole(user=admin, role=BaseRoleEnum.SUPERADMIN))

    with as_admin(client, str(admin.id), roles=["superadmin"]):
        response = client.delete("/api/users/123/")
        assert response.status_code == 204

As User Context Manager

as_user(client, user_id, additional_claims=None)

Context manager to set JWT authentication for a user in test requests.

Parameters:
  • client (FlaskClient) – Flask test client

  • user_id (str) – User ID to authenticate as (string representation of UUID)

  • additional_claims (dict) – Optional additional JWT claims to include in the token

Example:

with as_user(client, str(user.id), additional_claims={"custom_claim": "value"}):
    response = client.get("/api/users/me/")
    # Token will include custom_claim

As Admin Context Manager

as_admin(client, user_id, additional_claims=None, roles=None)

Context manager to set JWT authentication for an admin user in test requests.

This is a convenience wrapper around as_user() that automatically adds admin role claims to the JWT token.

Parameters:
  • client (FlaskClient) – Flask test client

  • user_id (str) – Admin user ID to authenticate as (string representation of UUID)

  • additional_claims (dict) – Optional additional JWT claims to include in the token

  • roles (list) – List of roles to assign (default: [“admin”]). Use [“superadmin”] for superadmin privileges.

Example:

# Default admin role
with as_admin(client, str(admin.id)):
    response = client.get("/api/users/")

# Superadmin role
with as_admin(client, str(admin.id), roles=["superadmin"]):
    response = client.delete("/api/users/123/")

Clear Registration

clear_registration()

Clear the custom user registration.

This is a proxy to flask_more_smorest.perms.user_context.clear_registration() for convenience in test files.

Useful for testing to reset to default JWT behavior after registering custom user classes or getters.

Example:

from flask_more_smorest.testing import clear_registration, init_fms

def test_with_custom_user():
    init_fms(user=MyUser)
    # ... test ...
    clear_registration()  # Reset for next test

Testing with Fixtures

Create reusable test fixtures:

import pytest
from flask_more_smorest.perms.models.defaults import User, UserRole, BaseRoleEnum
from flask_more_smorest.testing import as_user, as_admin

@pytest.fixture
def test_user(db_session):
    """Create a test user."""
    with User.bypass_perms():
        user = User(email="test@example.com", password="password123")
        user.save()
    return user

@pytest.fixture
def test_admin(db_session):
    """Create an admin user."""
    with User.bypass_perms():
        admin = User(email="admin@example.com", password="password123")
        admin.save()
        admin.roles.append(UserRole(user=admin, role=BaseRoleEnum.ADMIN))
    return admin

def test_authenticated_endpoint(client, test_user):
    """Test authenticated endpoint with fixture."""
    with as_user(client, str(test_user.id)):
        response = client.get("/api/users/me/")
        assert response.status_code == 200

def test_admin_endpoint(client, test_admin):
    """Test admin endpoint with fixture."""
    with as_admin(client, str(test_admin.id)):
        response = client.get("/api/users/")
        assert response.status_code == 200

Testing Permissions

Test permission-based access control:

from flask_more_smorest import BasePermsModel
from flask_more_smorest.testing import as_user, as_admin
from flask_more_smorest.sqla import db
from sqlalchemy.orm import Mapped, mapped_column

class Article(BasePermsModel):
    title: Mapped[str] = mapped_column(db.String(100))
    content: Mapped[str] = mapped_column(db.Text)
    is_public: Mapped[bool] = mapped_column(db.Boolean, default=False)

    def _can_read(self, current_user) -> bool:
        if self.is_public:
            return True
        return self.user_id == current_user.id if current_user else False

    def _can_write(self, current_user) -> bool:
        if not current_user:
            return False
        return self.user_id == current_user.id or current_user.is_admin

def test_article_permissions(client, db_session):
    with Article.bypass_perms():
        owner = User(email="owner@example.com", password="password123")
        owner.save()

        admin = User(email="admin@example.com", password="password123")
        admin.save()

        public_article = Article(title="Public", content="Content", is_public=True)
        public_article.save()

        private_article = Article(title="Private", content="Secret", is_public=False, user_id=owner.id)
        private_article.save()

    # Unauthenticated: can read public, not private
    response = client.get(f"/api/articles/{public_article.id}/")
    assert response.status_code == 200

    response = client.get(f"/api/articles/{private_article.id}/")
    assert response.status_code == 403

    # Owner can read private article
    with as_user(client, str(owner.id)):
        response = client.get(f"/api/articles/{private_article.id}/")
        assert response.status_code == 200

    # Admin can read all articles
    with as_admin(client, str(admin.id)):
        response = client.get(f"/api/articles/{private_article.id}/")
        assert response.status_code == 200

Testing Custom User Context

When using custom user context (see Custom User Context Integration), use clear_registration() to reset between tests:

from flask_more_smorest.testing import clear_registration, init_fms
from flask_more_smorest.perms import get_current_user

class MockUser:
    id = "test-id"

    def has_role(self, role: str) -> bool:
        return role == "admin"

    def list_roles(self) -> list[str]:
        return ["admin"]

def test_with_custom_user():
    # Register mock user
    init_fms(user=MockUser)

    # Test your code
    user = get_current_user()
    assert user.id == "test-id"

    # Clear for next test
    clear_registration()

def test_with_default_jwt():
    # Clear to get back default JWT behavior
    clear_registration()

    # Now use default User model
    user = User(email="test@example.com", password="password123")
    user.save()

    with as_user(client, str(user.id)):
        response = client.get("/api/users/me/")
        assert response.status_code == 200

Common Patterns

Test multiple roles:

def test_role_based_access(client, db_session):
    with User.bypass_perms():
        regular = User(email="regular@example.com", password="password123")
        regular.save()

        admin = User(email="admin@example.com", password="password123")
        admin.save()
        admin.roles.append(UserRole(user=admin, role=BaseRoleEnum.ADMIN))

        superadmin = User(email="superadmin@example.com", password="password123")
        superadmin.save()
        superadmin.roles.append(UserRole(user=superadmin, role=BaseRoleEnum.SUPERADMIN))

    # Regular user: 403 on admin endpoint
    with as_user(client, str(regular.id)):
        response = client.get("/api/users/")
        assert response.status_code == 403

    # Admin: 200 on admin endpoint
    with as_admin(client, str(admin.id)):
        response = client.get("/api/users/")
        assert response.status_code == 200

    # Superadmin: can delete
    with as_admin(client, str(superadmin.id), roles=["superadmin"]):
        response = client.delete(f"/api/users/{regular.id}/")
        assert response.status_code == 204

Test with additional claims:

def test_custom_claims(client, db_session):
    with User.bypass_perms():
        user = User(email="test@example.com", password="password123")
        user.save()

    # Test endpoint that reads custom claims
    with as_user(client, str(user.id), additional_claims={"tenant_id": "12345"}):
        response = client.get("/api/users/me/")
        assert response.status_code == 200
        # Endpoint can now access tenant_id from JWT claims

Test multiple requests in one context:

def test_multiple_authenticated_requests(client, db_session):
    with User.bypass_perms():
        user = User(email="test@example.com", password="password123")
        user.save()

    with as_user(client, str(user.id)):
        # All requests in this block are authenticated
        response1 = client.get("/api/users/me/")
        assert response1.status_code == 200

        response2 = client.get("/api/users/")
        assert response2.status_code == 200

        response3 = client.patch("/api/users/me/", json={"email": "new@example.com"})
        assert response3.status_code == 200

See also

Custom User Context Integration

Guide for integrating with external authentication systems.

Permissions System

Learn about the permission system and how it works with user context.