Mastering API Request Wrappers in Pytest: Reusable GET, POST, PUT, DELETE

Learn how to boost code reuse and maintainability in API automation by encapsulating common HTTP methods (GET, POST, PUT, DELETE, etc.) within a Pytest-friendly RequestUtil class, complete with logging, exception handling, session management, dynamic headers, and timeout support, plus practical test examples.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Mastering API Request Wrappers in Pytest: Reusable GET, POST, PUT, DELETE

Why Wrap Request Methods

In API automation testing, repeatedly writing raw requests calls leads to duplicated code and makes maintenance difficult. Encapsulating HTTP verbs in a utility class hides request details, reduces repetition, improves readability, and centralizes changes such as base URLs or headers.

Setup and Basic Wrapper

1. Install the required library pip install requests 2. Create request_util.py and define the RequestUtil class

import requests
import json

class RequestUtil:
    def __init__(self, base_url):
        self.base_url = base_url

    def get(self, endpoint, params=None, headers=None):
        """Send a GET request"""
        url = f"{self.base_url}{endpoint}"
        response = requests.get(url, params=params, headers=headers)
        return response

    def post(self, endpoint, data=None, json=None, headers=None):
        """Send a POST request"""
        url = f"{self.base_url}{endpoint}"
        response = requests.post(url, data=data, json=json, headers=headers)
        return response

    def put(self, endpoint, data=None, json=None, headers=None):
        """Send a PUT request"""
        url = f"{self.base_url}{endpoint}"
        response = requests.put(url, data=data, json=json, headers=headers)
        return response

    def delete(self, endpoint, headers=None):
        """Send a DELETE request"""
        url = f"{self.base_url}{endpoint}"
        response = requests.delete(url, headers=headers)
        return response

    def patch(self, endpoint, data=None, json=None, headers=None):
        """Send a PATCH request"""
        url = f"{self.base_url}{endpoint}"
        response = requests.patch(url, data=data, json=json, headers=headers)
        return response

    def options(self, endpoint, headers=None):
        """Send an OPTIONS request"""
        url = f"{self.base_url}{endpoint}"
        response = requests.options(url, headers=headers)
        return response

    def head(self, endpoint, headers=None):
        """Send a HEAD request"""
        url = f"{self.base_url}{endpoint}"
        response = requests.head(url, headers=headers)
        return response

Using the Wrapped Class in Pytest

Define a fixture that creates a RequestUtil instance and write test functions for each HTTP verb.

import pytest
from request_util import RequestUtil

@pytest.fixture
def api():
    return RequestUtil("https://api.example.com")

def test_get_user_info(api):
    response = api.get("/users/123")
    assert response.status_code == 200
    assert response.json()["name"] == "John Doe"

def test_create_user(api):
    user_data = {"name": "John Doe", "email": "[email protected]"}
    response = api.post("/users", json=user_data)
    assert response.status_code == 201
    assert response.json()["name"] == "John Doe"

def test_update_user(api):
    user_data = {"name": "John Doe Updated", "email": "[email protected]"}
    response = api.put("/users/123", json=user_data)
    assert response.status_code == 200
    assert response.json()["name"] == "John Doe Updated"

def test_delete_user(api):
    response = api.delete("/users/123")
    assert response.status_code == 204

def test_partial_update_user(api):
    user_data = {"email": "[email protected]"}
    response = api.patch("/users/123", json=user_data)
    assert response.status_code == 200
    assert response.json()["email"] == "[email protected]"

def test_options_user(api):
    response = api.options("/users/123")
    assert response.status_code == 200
    assert "Allow" in response.headers

def test_head_user(api):
    response = api.head("/users/123")
    assert response.status_code == 200
    assert response.headers["Content-Type"] == "application/json"

Advanced Techniques

Logging

import logging

class RequestUtil:
    def __init__(self, base_url):
        self.base_url = base_url
        self.logger = logging.getLogger(__name__)

    def get(self, endpoint, params=None, headers=None):
        url = f"{self.base_url}{endpoint}"
        self.logger.info(f"Sending GET request to {url}")
        response = requests.get(url, params=params, headers=headers)
        self.logger.info(f"Response status code: {response.status_code}")
        self.logger.info(f"Response content: {response.text}")
        return response
    # Other methods follow the same pattern

Exception Handling

import logging
import requests

class RequestUtil:
    def __init__(self, base_url):
        self.base_url = base_url
        self.logger = logging.getLogger(__name__)

    def get(self, endpoint, params=None, headers=None):
        url = f"{self.base_url}{endpoint}"
        try:
            self.logger.info(f"Sending GET request to {url}")
            response = requests.get(url, params=params, headers=headers)
            response.raise_for_status()
            self.logger.info(f"Response status code: {response.status_code}")
            self.logger.info(f"Response content: {response.text}")
            return response
        except requests.exceptions.RequestException as e:
            self.logger.error(f"Request failed: {e}")
            raise

Session Management

class RequestUtil:
    def __init__(self, base_url):
        self.base_url = base_url
        self.session = requests.Session()
        self.logger = logging.getLogger(__name__)

    def get(self, endpoint, params=None, headers=None):
        url = f"{self.base_url}{endpoint}"
        self.logger.info(f"Sending GET request to {url}")
        response = self.session.get(url, params=params, headers=headers)
        self.logger.info(f"Response status code: {response.status_code}")
        self.logger.info(f"Response content: {response.text}")
        return response
    # Other methods are analogous

Dynamic Header Setting

class RequestUtil:
    def __init__(self, base_url):
        self.base_url = base_url
        self.session = requests.Session()
        self.logger = logging.getLogger(__name__)

    def set_headers(self, headers):
        """Dynamically update request headers"""
        self.session.headers.update(headers)

Timeout Support

class RequestUtil:
    def __init__(self, base_url, timeout=10):
        self.base_url = base_url
        self.timeout = timeout
        self.session = requests.Session()
        self.logger = logging.getLogger(__name__)

    def get(self, endpoint, params=None, headers=None):
        url = f"{self.base_url}{endpoint}"
        self.logger.info(f"Sending GET request to {url}")
        response = self.session.get(url, params=params, headers=headers, timeout=self.timeout)
        self.logger.info(f"Response status code: {response.status_code}")
        self.logger.info(f"Response content: {response.text}")
        return response
    # Other methods follow the same pattern

Conclusion

Encapsulating HTTP request methods into a reusable RequestUtil class dramatically improves the maintainability and readability of API automation tests. By adding logging, exception handling, session reuse, dynamic headers, and configurable timeouts, the wrapper becomes a robust foundation for scalable test suites.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PythonAutomationloggingAPI testingHTTP requestspytestrequest wrapper
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.