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:
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.