Testing
This guide covers patterns for testing code that uses Application Kit.
Test Markers
Application Kit tests use pytest markers to categorize tests:
import pytest
@pytest.mark.django
def test_django_feature():
...
@pytest.mark.fastapi
def test_fastapi_feature():
...
@pytest.mark.ninja
def test_ninja_feature():
...
@pytest.mark.authenticator
def test_authenticator_feature():
...
Mocking Authentication
Creating Mock Tokens
To test authenticated endpoints, create mock project tokens:
from application_kit.authenticator.types import (
KeyOrganizationModel,
ProjectReference,
ProjectTokenModel,
PublicKeyTokenInstanceModel,
)
def make_project_token(org_id: int = 1, project_id: int = 1) -> ProjectTokenModel:
return ProjectTokenModel(
PublicKeyTokenInstanceModel(
domains=["*"],
products=[],
organization=KeyOrganizationModel(pk=org_id, project_pk=project_id),
),
reference=ProjectReference(project_id=project_id, organization_id=org_id),
)
Attaching Tokens to Requests
For Django views, attach the token directly to the request:
from django.http import HttpRequest
def make_authenticated_request(org_id: int = 1, project_id: int = 1) -> HttpRequest:
request = HttpRequest()
request.method = "GET"
request.project_token = make_project_token(org_id, project_id)
return request
Mocking Redis
Rate Limiting Tests
Mock Redis to control rate limit behavior:
from unittest.mock import MagicMock, patch
@patch("application_kit.django.ratelimit.get_redis_instance")
def test_rate_limit_allows_request(mock_get_redis):
# Setup mock Redis
mock_redis = MagicMock()
mock_script = MagicMock()
# Return: [is_over_limit, request_count, ttl, max_requests]
mock_script.return_value = [0, 1, 60, 10]
mock_redis.register_script.return_value = mock_script
mock_get_redis.return_value = mock_redis
# Test your view
...
Async Rate Limiting Tests
For async views, use async-compatible mocks:
@pytest.mark.asyncio
@patch("application_kit.django.ratelimit.get_async_redis_instance")
async def test_async_rate_limit(mock_get_redis):
mock_redis = MagicMock()
mock_script = MagicMock()
async def async_script_call(*args):
return [0, 1, 60, 10]
mock_script.return_value = async_script_call()
mock_redis.register_script.return_value = mock_script
mock_get_redis.return_value = mock_redis
# Test your async view
...
Test Builders
Application Kit provides builder utilities for creating test objects.
RequestBuilder
Build Django HTTP requests with fluent API:
from tests.builder.request import RequestBuilder
# Simple GET request
request = RequestBuilder().get()
# GET with query params and headers
request = RequestBuilder().get(
query={"key": "test_api_key"},
headers={"Origin": "https://example.com"}
)
# POST request
request = RequestBuilder().post(
query={"key": "test_api_key"},
headers={"Content-Type": "application/json"},
)
# Fluent builder pattern
request = (
RequestBuilder()
.with_method("GET")
.with_path("/api/v1/endpoint")
.with_query_params({"key": "test_key"})
.with_headers({"Origin": "https://example.com"})
.build()
)
ViewBuilder
Build mock Django views for decorator testing:
from django.http import JsonResponse
from tests.builder.view import ViewBuilder
# Simple view
view = ViewBuilder().with_response(JsonResponse({"status": "ok"})).build()
# View with additional arguments
view = (
ViewBuilder()
.with_response(JsonResponse({"status": "ok"}))
.with_args(["readable_token"])
.build()
)
# Mock view for assertions
mock_view = (
ViewBuilder()
.with_response(JsonResponse({"status": "ok"}))
.build(mock=True)
)
# Call and verify
mock_view(request)
mock_view.assert_called_once()
Testing Patterns
Testing Decorated Views
from unittest.mock import patch, MagicMock
from django.http import JsonResponse
from application_kit.django.decorators import authenticate_key
@patch("application_kit.django.decorators.validate_request")
def test_authenticated_view(mock_validate):
# Setup mock authentication
mock_validate.return_value = make_project_token()
@authenticate_key()
def my_view(request):
return JsonResponse({"status": "ok"})
request = RequestBuilder().get(query={"key": "test_key"})
response = my_view(request)
assert response.status_code == 200
Testing Permission Classes
from application_kit.authenticator.permissions import IsPublicKeyPermission
from application_kit.authenticator.exceptions import PermissionDenied
import pytest
def test_public_key_permission_passes():
token = make_project_token() # Creates a public key token
headers = {}
# Should not raise
IsPublicKeyPermission.check(token, headers)
def test_public_key_permission_fails_for_user_token():
token = make_user_token() # Would need a user token factory
headers = {}
with pytest.raises(PermissionDenied):
IsPublicKeyPermission.check(token, headers)
Running Tests
# Run all tests
hatch run tests:all
# Run specific test file
hatch run +python=3.12 tests:pytest tests/unit/django/ratelimit_test.py
# Run specific test
hatch run +python=3.12 tests:pytest tests/unit/django/ratelimit_test.py::test_name
# Run tests by marker
hatch run +python=3.12 tests:pytest -m django
hatch run +python=3.12 tests:pytest -m fastapi