Backend Development 6 min read

Encapsulating Custom Exceptions and Logging in an API Automation Framework

This article explains how to improve the stability and maintainability of API automated tests by defining custom exception classes, wrapping a Loguru‑based logger, and integrating both into request utilities and pytest test cases with clear code examples.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Encapsulating Custom Exceptions and Logging in an API Automation Framework

In API automated testing, proper exception handling and logging are essential for test stability and maintainability; encapsulating custom exceptions and a logger makes problem tracing clearer and improves code readability.

1. Defining custom exception classes – a base APIException class is created in exceptions.py , followed by specific subclasses APIRequestException , APIResponseException , and APIDataException to represent different error scenarios.

class APIException(Exception):
    """Base exception class"""
    pass

class APIRequestException(APIException):
    """Request exception"""
    pass

class APIResponseException(APIException):
    """Response exception"""
    pass

class APIDataException(APIException):
    """Data exception"""
    pass

2. Encapsulating the logger – the Loguru library is installed ( pip install loguru ) and a Logger class is defined with a static setup_logging method that adds a rotating log file (100 MB, zipped) and returns the configured logger.

from loguru import logger

class Logger:
    @staticmethod
    def setup_logging(log_file="logs/log_{time}.log"):
        """Configure the logger"""
        logger.add(log_file, rotation="100 MB", compression="zip")
        return logger

logger = Logger.setup_logging()

3. Using custom exceptions and the logger in request utilities – in request_util.py , request methods are wrapped with try/except blocks; successful steps log info, failures log errors and raise APIRequestException . Both GET and POST examples are shown.

import requests
from exceptions import APIRequestException, APIResponseException
from logger import logger

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

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

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

4. Applying the utilities in pytest test cases – a test_api.py file defines a fixture that creates a RequestUtil instance, then writes test functions for GET and POST operations. Assertions verify status codes and response data; any APIRequestException is caught, logged, and causes the test to fail.

import pytest
from request_util import RequestUtil

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

def test_get_user_info(api):
    try:
        response = api.get("/users/123")
        assert response.status_code == 200
        assert response.json()["name"] == "John Doe"
    except APIRequestException as e:
        logger.error(f"Test failed: {e}")
        pytest.fail(f"Test failed: {e}")

def test_create_user(api):
    try:
        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"
    except APIRequestException as e:
        logger.error(f"Test failed: {e}")
        pytest.fail(f"Test failed: {e}")

Conclusion – By encapsulating custom exceptions and a Loguru‑based logger, API automation tests become more robust and easier to maintain; exceptions provide clear error categorisation, while detailed logs facilitate debugging and issue tracking.

backendPythonautomationloggingAPI testingCustom Exceptions
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

0 followers
Reader feedback

How this landed with the community

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