Backend Development 9 min read

Python API Automation Testing Suite with requests, pytest, and pytest‑html

This guide demonstrates how to build a Python API automation testing suite using requests, pytest, and pytest‑html, covering environment setup, project structure, common utilities, test case implementation, advanced assertions, error handling, test data management, and HTML report generation.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Python API Automation Testing Suite with requests, pytest, and pytest‑html

In Python, the requests library can be used for API automation testing, and testing frameworks such as unittest or pytest can organize and run test suites.

Installation

pip install requests pytest

Project directory structure

project/
│
├── common/           # public methods module
│   └── utils.py      # request, assertion utilities
├── conf/             # configuration module
│   └── config.py     # environment and base URL
├── data/             # test data module
│   └── test_data.json # input data for test cases
├── log/              # log module
│   └── log.txt       # runtime logs
├── report/           # report module
│   └── report.html   # generated HTML report
├── test_case/        # test case modules
│   ├── test_login.py # login API tests
│   ├── test_signup.py# signup API tests
│   └── ...           # other API tests
└── testsuite.py      # test suite runner

common/utils.py – common functions

import requests
import json
def send_request(method, url, headers=None, params=None, data=None):
response = requests.request(method, url, headers=headers, params=params, data=data)
response.raise_for_status()  # raise if status not 200
return response.json()
def assert_response(response_data, expected_key, expected_value):
assert expected_key in response_data, f"Expected key '{expected_key}' not found in response."
assert response_data[expected_key] == expected_value, f"Expected value for '{expected_key}' is '{expected_value}', but got '{response_data[expected_key]}'"

conf/config.py – configuration

TEST_ENVIRONMENT = "development"
BASE_URL = "http://localhost:8000/api/"

test_case/test_login.py – example test case

import json
from project.common.utils import send_request, assert_response
from project.conf.config import BASE_URL
class TestLogin:
def test_successful_login(self):
url = f"{BASE_URL}login"
data = {"username": "test_user", "password": "test_password"}
response_data = send_request("POST", url, data=json.dumps(data))
assert_response(response_data, "status", "success")
assert_response(response_data, "message", "Logged in successfully.")
def test_invalid_credentials(self):
url = f"{BASE_URL}login"
data = {"username": "invalid_user", "password": "invalid_password"}
response_data = send_request("POST", url, data=json.dumps(data))
assert_response(response_data, "status", "error")
assert_response(response_data, "message", "Invalid credentials.")

testsuite.py – organize and run test cases

import pytest
from project.test_case import test_login, test_signup  # other test modules
@pytest.mark.parametrize("test_case_module", [test_login, test_signup])
def test_suite(test_case_module):
suite = unittest.TestLoader().loadTestsFromModule(test_case_module)
runner = unittest.TextTestRunner()
results = runner.run(suite)
assert results.wasSuccessful(), "Test suite failed."
if __name__ == "__main__":
pytest.main(["--html=report/report.html", "--self-contained-html"])

Run the suite with:

pytest testsuite.py

Advanced assertions

def assert_in_response(response_data, key, expected_values):
assert key in response_data, f"Expected key '{key}' not found in response."
assert response_data[key] in expected_values, f"Expected value for '{key}' to be one of {expected_values}, but got '{response_data[key]}'"

Error handling in send_request

def send_request(method, url, headers=None, params=None, data=None):
try:
response = requests.request(method, url, headers=headers, params=params, data=data)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as http_error:
logging.error(f"HTTP error occurred: {http_error}")
raise http_error
except Exception as e:
logging.error(f"Unexpected error occurred: {e}")
raise e

Test data management

# data/test_data.py
LOGIN_TEST_DATA = {
"valid_credentials": {"username": "test_user", "password": "test_password"},
"invalid_credentials": {"username": "invalid_user", "password": "invalid_password"}
}

Use the data in test cases:

from project.data.test_data import LOGIN_TEST_DATA
class TestLogin:
def test_successful_login(self):
url = f"{BASE_URL}login"
data = LOGIN_TEST_DATA["valid_credentials"]
response_data = send_request("POST", url, data=json.dumps(data))
assert_response(response_data, "status", "success")
assert_response(response_data, "message", "Logged in successfully.")
def test_invalid_credentials(self):
url = f"{BASE_URL}login"
data = LOGIN_TEST_DATA["invalid_credentials"]
response_data = send_request("POST", url, data=json.dumps(data))
assert_response(response_data, "status", "error")
assert_response(response_data, "message", "Invalid credentials.")

Report generation with pytest-html

pip install pytest-html

Configure in testsuite.py :

import pytest
from pytest_html_reporter import attach_extra_css, add_context
@pytest.mark.parametrize("test_case_module", [test_login, test_signup])
def test_suite(test_case_module):
# ... run tests ...
if __name__ == "__main__":
pytest.main(["--html=report/report.html", "--self-contained-html"])
attach_extra_css("custom.css")
add_context({"project_name": "My API Test Project"})

Running the suite generates a report.html file with the test results.

BackendpythonautomationAPI testingRequestspytestTest Suite
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.