CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-webargs

Declarative parsing and validation of HTTP request objects, with built-in support for popular web frameworks.

Overview
Eval results
Files

testing-utilities.mddocs/

Testing Utilities

Testing support including base test classes for parser validation, common test scenarios, and utilities for testing request parsing across different web frameworks. The testing utilities provide a standardized approach to validating parser behavior and ensuring consistent functionality across framework implementations.

Capabilities

Common Test Case Base Class

Base test class that defines standard test methods for validating parser functionality across different web frameworks.

class CommonTestCase:
    """
    Base test class for validating parser functionality.
    
    Provides common test methods that should work across all framework parsers.
    Subclasses must implement create_app() to return a WSGI-compatible application.
    
    Methods:
        create_app(): Abstract method to create test application
        create_testapp(app): Create webtest.TestApp wrapper
        before_create_app(): Hook called before app creation
        after_create_app(): Hook called after app creation
    """
    
    def create_app(self):
        """
        Create and return a WSGI-compatible test application.
        
        Must be implemented by subclasses to create framework-specific test app
        with routes for testing parser functionality.
        
        Returns:
            WSGI application: Test application with parser routes
            
        Raises:
            NotImplementedError: If not implemented by subclass
        """
        raise NotImplementedError("Must define create_app()")
    
    def create_testapp(self, app):
        """
        Create webtest.TestApp wrapper for testing.
        
        Args:
            app: WSGI application to wrap
            
        Returns:
            webtest.TestApp: Test client for making requests
        """
        return webtest.TestApp(app)
    
    def before_create_app(self):
        """Hook called before application creation. Override for setup."""
        pass
    
    def after_create_app(self):
        """Hook called after application creation. Override for teardown."""
        pass

Test Fixtures

Pytest fixtures for setting up test applications and clients.

@pytest.fixture(scope="class")
def testapp(self):
    """
    Pytest fixture that provides a test application client.
    
    Manages the lifecycle of test application creation and cleanup.
    Calls before_create_app(), creates the app, wraps it in TestApp,
    and calls after_create_app() for cleanup.
    
    Yields:
        webtest.TestApp: Test client for making HTTP requests
    """

Standard Test Methods

Common test methods that validate core parsing functionality.

def test_parse_querystring_args(self, testapp):
    """
    Test parsing arguments from query string parameters.
    
    Validates that query parameters are correctly parsed and validated
    according to the defined schema.
    
    Expected test route: GET /echo?name=Fred -> {"name": "Fred"}
    """

def test_parse_form(self, testapp):
    """
    Test parsing arguments from form data.
    
    Validates that form-encoded data is correctly parsed and validated.
    
    Expected test route: POST /echo_form with form data -> parsed JSON
    """

def test_parse_json(self, testapp):
    """
    Test parsing arguments from JSON request body.
    
    Validates that JSON request bodies are correctly parsed and validated.
    
    Expected test route: POST /echo_json with JSON body -> parsed JSON
    """

Usage Examples

Creating Framework-Specific Test Cases

import pytest
from webargs.testing import CommonTestCase
from webargs import fields
from webargs.flaskparser import use_args
from flask import Flask, jsonify

class TestFlaskParser(CommonTestCase):
    """Test case for Flask parser using CommonTestCase."""
    
    def create_app(self):
        """Create Flask test application with parser routes."""
        app = Flask(__name__)
        
        @app.route("/echo")
        @use_args({"name": fields.Str()}, location="query")
        def echo(args):
            return jsonify(args)
        
        @app.route("/echo_form", methods=["POST"])
        @use_args({"name": fields.Str()}, location="form")
        def echo_form(args):
            return jsonify(args)
        
        @app.route("/echo_json", methods=["POST"])
        @use_args({"name": fields.Str()}, location="json")
        def echo_json(args):
            return jsonify(args)
        
        return app
    
    def test_custom_validation(self, testapp):
        """Additional test specific to Flask implementation."""
        response = testapp.get("/echo?name=TestUser")
        assert response.json == {"name": "TestUser"}

Django Test Case Example

from django.conf import settings
from django.test import RequestFactory
from webargs.testing import CommonTestCase
from webargs.djangoparser import use_args
from webargs import fields

class TestDjangoParser(CommonTestCase):
    """Test case for Django parser."""
    
    def before_create_app(self):
        """Configure Django settings before app creation."""
        if not settings.configured:
            settings.configure(
                SECRET_KEY='test-key',
                ROOT_URLCONF='test_urls',
            )
    
    def create_app(self):
        """Create Django test application."""
        from django.http import JsonResponse
        from django.urls import path
        
        @use_args({"name": fields.Str()}, location="query")
        def echo_view(request, args):
            return JsonResponse(args)
        
        # Return WSGI application
        from django.core.wsgi import get_wsgi_application
        return get_wsgi_application()

Async Framework Testing

import asyncio
from webargs.testing import CommonTestCase
from webargs.aiohttpparser import use_args
from webargs import fields
from aiohttp import web

class TestAIOHTTPParser(CommonTestCase):
    """Test case for aiohttp parser with async support."""
    
    def create_app(self):
        """Create aiohttp test application."""
        app = web.Application()
        
        @use_args({"name": fields.Str()}, location="query")
        async def echo_handler(request, args):
            return web.json_response(args)
        
        app.router.add_get("/echo", echo_handler)
        return app
    
    def create_testapp(self, app):
        """Create async-compatible test client."""
        import webtest_aiohttp
        return webtest_aiohttp.TestApp(app)

Custom Validation Testing

class TestCustomValidation(CommonTestCase):
    """Test custom validation scenarios."""
    
    def create_app(self):
        app = Flask(__name__)
        
        def validate_positive(value):
            if value <= 0:
                raise ValidationError("Must be positive")
            return True
        
        @app.route("/validate")
        @use_args({
            "number": fields.Int(validate=validate_positive)
        }, location="query")
        def validate_endpoint(args):
            return jsonify(args)
        
        return app
    
    def test_positive_validation(self, testapp):
        """Test custom positive number validation."""
        # Valid positive number
        response = testapp.get("/validate?number=5")
        assert response.json == {"number": 5}
        
        # Invalid negative number should return 422
        response = testapp.get("/validate?number=-1", expect_errors=True)
        assert response.status_code == 422

Error Handling Testing

class TestErrorHandling(CommonTestCase):
    """Test error handling scenarios."""
    
    def create_app(self):
        app = Flask(__name__)
        
        @app.route("/required")
        @use_args({"name": fields.Str(required=True)}, location="query")
        def required_endpoint(args):
            return jsonify(args)
        
        return app
    
    def test_missing_required_field(self, testapp):
        """Test handling of missing required fields."""
        response = testapp.get("/required", expect_errors=True)
        assert response.status_code == 422
        assert "required" in response.json.get("messages", {}).get("query", {}).get("name", [])
    
    def test_validation_error_structure(self, testapp):
        """Test structure of validation error responses."""
        response = testapp.get("/required", expect_errors=True)
        
        # Verify error message structure
        assert "messages" in response.json
        assert "query" in response.json["messages"]
        assert isinstance(response.json["messages"]["query"], dict)

Multi-Location Parsing Tests

class TestMultiLocationParsing(CommonTestCase):
    """Test parsing from multiple request locations."""
    
    def create_app(self):
        app = Flask(__name__)
        
        @app.route("/multi", methods=["POST"])
        @use_args({
            "name": fields.Str(location="json"),
            "page": fields.Int(location="query", missing=1),
            "token": fields.Str(location="headers", data_key="Authorization")
        })
        def multi_location_endpoint(args):
            return jsonify(args)
        
        return app
    
    def test_multi_location_parsing(self, testapp):
        """Test parsing from JSON, query, and headers simultaneously."""
        response = testapp.post_json(
            "/multi?page=2",
            {"name": "test"},
            headers={"Authorization": "Bearer token123"}
        )
        
        expected = {
            "name": "test",
            "page": 2,
            "token": "Bearer token123"
        }
        assert response.json == expected

Test Utilities

Helper Functions

def create_test_request(framework, method="GET", path="/", **kwargs):
    """
    Create framework-specific test request object.
    
    Args:
        framework (str): Framework name ("flask", "django", etc.)
        method (str): HTTP method
        path (str): Request path
        **kwargs: Additional request parameters
        
    Returns:
        Request object appropriate for the framework
    """

def assert_validation_error(response, field_name, location="json"):
    """
    Assert that response contains validation error for specific field.
    
    Args:
        response: Test response object
        field_name (str): Name of field with validation error
        location (str): Request location where error occurred
    """

Integration with Testing Frameworks

Pytest Integration

# conftest.py
import pytest
from webargs.testing import CommonTestCase

class BaseParserTest(CommonTestCase):
    """Base class for all parser tests."""
    
    @pytest.fixture(autouse=True)
    def setup_test_data(self):
        """Setup test data before each test."""
        self.test_user = {"name": "Test User", "age": 25}

Unittest Integration

import unittest
from webargs.testing import CommonTestCase

class TestParser(CommonTestCase, unittest.TestCase):
    """Integration with unittest framework."""
    
    def setUp(self):
        """Setup method called before each test."""
        super().setUp()
        self.test_data = {"key": "value"}
    
    def tearDown(self):
        """Cleanup method called after each test."""
        super().tearDown()

Types

TestApp = webtest.TestApp
WSGIApplication = typing.Callable[[dict, typing.Callable], typing.Iterable[bytes]]

Install with Tessl CLI

npx tessl i tessl/pypi-webargs

docs

core-parsing.md

field-types.md

framework-parsers.md

index.md

testing-utilities.md

tile.json