Skip to content

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