Skip to main content

Command Palette

Search for a command to run...

#5 - Pytest API Automation Framework - Setup

Updated
3 min read
#5 - Pytest API Automation Framework - Setup
S

• Possess over 14 years of experience in Quality Engineering, specializing in building test frameworks for complex systems including web applications, REST APIs, and core cloud infrastructure on platforms like Akamai's Linode and Azure.

• Skilled in the development and improvement of automation frameworks for various Web applications, service APIs displaying proficiency in TestNG, PyTest, Selenium, Rest Assured, Jenkins pipeline.

This article explains a pytest-based API automation framework. The framework is divided into three clear parts:

The framework follows a 3-layer architecture:

  • Core – low-level HTTP and logging

  • Application – business logic and payloads

  • Tests – pytest tests and fixtures

Code - https://github.com/sksingh329/gorest-api-test.git


Framework Architecture


Core Layer – The Foundation

The core layer handles everything related to HTTP communication and logging.

gorest-api-test/
├── core/
│   ├── api/
│   │   └── api_client.py → APIClient
│   └── utils/
│       └── http_logger.py

core/api/api_client.py

This is a thin wrapper over the requests library.

  • Make HTTP calls (GET, POST, PUT, DELETE)

  • Attach headers and base URL

  • Return raw responses

utils/http_logger.py → Handles request and response logging.

class APIClient:
    def __init__(self, base_url, timeout=30, headers=None):
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.headers = headers or {}

    def get(self, path, headers=None, params=None):
        url = f"{self.base_url}/{path.lstrip('/')}"
        final_headers = {**self.headers, **(headers or {})}

        log_request(
            method="GET",
            url=url,
            params=params,
            headers=final_headers,
        )

        response = requests.get(
            url, 
            headers=final_headers,
            params=params,
            timeout=self.timeout
        )
        log_response(response)
        return response

Application Layer – Business Logic

gorest-api-test/
├── application/
│   ├── user_client.py
│   ├── payload/
│   │   └── user_payload.py

This layer represents how your application behaves, not how HTTP works.

application/user_client.py

  • This is where business logic defined which tests will call for setup and performing test action.

payloads/user_payload.py → Handles test data creation.

class UserClient:
    def __init__(self, api_client):
        self.api_client = api_client

    def list_user(self):
        return self.api_client.get(USERS_ENDPOINT)

    def create_user(self, payload):
        return self.api_client.post(USERS_ENDPOINT, body=payload)

    def get_user(self, user_id):
        return self.api_client.get(USER_ENDPOINT.format(user_id=user_id))

    def update_user(self, user_id, payload):
        return self.api_client.put(USER_ENDPOINT.format(user_id=user_id), body=payload)

    def delete_user(self, user_id):
        return self.api_client.delete(USER_ENDPOINT.format(user_id=user_id))

Tests Layer – Where Assertions Live

├── tests/
│   ├── conftest.py
│   ├── test_users_collection.py
│   ├── user/
│   │   ├── conftest.py
│   │   └── test_user_resource.py

tests/conftest.py → Global pytest configuration and common fixtures.

tests/users/conftest.py → User-specific fixtures.

Test Files

conftest.py

@pytest.fixture(scope="session")
def auth_headers():
    token = os.getenv("API_TOKEN")
    return {
        "Authorization": f"Bearer {token}"
    }

@pytest.fixture
def api_client(auth_headers):
    return APIClient(
        base_url=BASE_URL, 
        timeout=20,
        headers=auth_headers
    )

@pytest.fixture
def user_client(api_client):
    return UserClient(api_client=api_client)

test_users_collection.py

def test_create_user(user_client):
    payload = create_user_payload(status="inactive")

    response = user_client.create_user(payload=payload)
    response_body = response.json()

    assert response.status_code == 201
    assert response_body["name"] == payload["name"]
    assert response_body["email"] == payload["email"]
    assert response_body["gender"] == payload["gender"]
    assert response_body["status"] == payload["status"]

    user_id = response_body["id"]

    logger.info(f"User ID: {user_id}")

    if user_id:
        user_client.delete_user(user_id=user_id)

users/conftest.py

@pytest.fixture
def user_fixture(user_client):
    payload = user_create_payload()
    response = user_client.create_user(payload=payload)
    assert response.status_code == 201 
    response_body = response.json()
    user_id = response_body["id"]
    logger.info(f"Created user with ID: {user_id}")
    yield user_id
    user_client.delete_user(user_id=user_id)
    logger.info(f"Deleted user with ID: {user_id}")

users/test_user_resource.py

def test_get_user(user_client, user_fixture):
    user_id = user_fixture
    response = user_client.get_user(user_id=user_id)
    assert response.status_code == 200

def test_update_user(user_client, user_fixture):
    # Update user status to active
    payload = user_update_payload(status="active")
    user_id = user_fixture
    response = user_client.update_user(user_id=user_id, payload=payload)
    assert response.status_code == 200
    assert response.json()["status"] == "active"

Execution Flow

  • pytest starts

  • Fixtures are loaded from conftest.py

  • UserClient uses APIClient

  • APIClient makes HTTP calls

  • Requests & responses are logged

  • Tests assert results


Running tests

# Setup 
git clone https://github.com/sksingh329/gorest-api-test.git
cd gorest-api-test
uv sync 
export API_TOKEN="<<go_rest_api_token>>"

# Running tests
uv run pytest

API Testing | Pytest & requests

Part 5 of 5

A practical series on building a scalable, production-ready API testing framework using Python, requests and Pytest. Covers modern project setup, clean architecture, real-world API scenarios, and CI integration.

Start from the beginning

#1 - GoRest CRUD APIs with curl

Basics of HTTP and REST APIs RFC 9110: HTTP Semantics RFC 9112: HTTP/1.1 Introduction To API Introduction to REST API What is cURL? cURL stands for Client URL. It is an open-source project focused on building reliable Internet data-transfer to...