How to Wrap HTTP Request Methods in Pytest for Cleaner API Tests
This guide explains why encapsulating HTTP request methods in Pytest improves code reuse and maintainability, and provides complete Python examples for GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD, and custom requests, plus advanced tips on logging, exception handling, and session management.
Why encapsulate request methods
In API automation testing, repeatedly calling requests directly leads to duplicated code and fragile test cases. Wrapping common HTTP verbs in a utility class reduces repetition, centralises URL and header changes, and makes test scripts easier to read and maintain.
Basic request wrappers
GET
import requests
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}"
response = requests.get(url, params=params, headers=headers)
return response
# Example usage
if __name__ == "__main__":
request_util = RequestUtil("https://api.example.com")
response = request_util.get("/users", params={"page": 1, "limit": 10})
print(response.status_code)
print(response.json())POST
import requests
class RequestUtil:
def __init__(self, base_url):
self.base_url = base_url
def post(self, endpoint, data=None, json=None, headers=None):
url = f"{self.base_url}{endpoint}"
response = requests.post(url, data=data, json=json, headers=headers)
return response
# Example usage
if __name__ == "__main__":
request_util = RequestUtil("https://api.example.com")
response = request_util.post(
"/users",
json={"name": "John Doe", "email": "[email protected]"},
headers={"Content-Type": "application/json"}
)
print(response.status_code)
print(response.json())PUT
import requests
class RequestUtil:
def __init__(self, base_url):
self.base_url = base_url
def put(self, endpoint, data=None, json=None, headers=None):
url = f"{self.base_url}{endpoint}"
response = requests.put(url, data=data, json=json, headers=headers)
return response
# Example usage
if __name__ == "__main__":
request_util = RequestUtil("https://api.example.com")
response = request_util.put(
"/users/123",
json={"name": "John Doe Updated", "email": "[email protected]"},
headers={"Content-Type": "application/json"}
)
print(response.status_code)
print(response.json())DELETE
import requests
class RequestUtil:
def __init__(self, base_url):
self.base_url = base_url
def delete(self, endpoint, headers=None):
url = f"{self.base_url}{endpoint}"
response = requests.delete(url, headers=headers)
return response
# Example usage
if __name__ == "__main__":
request_util = RequestUtil("https://api.example.com")
response = request_util.delete("/users/123", headers={"Content-Type": "application/json"})
print(response.status_code)
print(response.json())PATCH
import requests
class RequestUtil:
def __init__(self, base_url):
self.base_url = base_url
def patch(self, endpoint, data=None, json=None, headers=None):
url = f"{self.base_url}{endpoint}"
response = requests.patch(url, data=data, json=json, headers=headers)
return response
# Example usage
if __name__ == "__main__":
request_util = RequestUtil("https://api.example.com")
response = request_util.patch(
"/users/123",
json={"email": "[email protected]"},
headers={"Content-Type": "application/json"}
)
print(response.status_code)
print(response.json())OPTIONS
import requests
class RequestUtil:
def __init__(self, base_url):
self.base_url = base_url
def options(self, endpoint, headers=None):
url = f"{self.base_url}{endpoint}"
response = requests.options(url, headers=headers)
return response
# Example usage
if __name__ == "__main__":
request_util = RequestUtil("https://api.example.com")
response = request_util.options("/users", headers={"Content-Type": "application/json"})
print(response.status_code)
print(response.headers)HEAD
import requests
class RequestUtil:
def __init__(self, base_url):
self.base_url = base_url
def head(self, endpoint, headers=None):
url = f"{self.base_url}{endpoint}"
response = requests.head(url, headers=headers)
return response
# Example usage
if __name__ == "__main__":
request_util = RequestUtil("https://api.example.com")
response = request_util.head("/users", headers={"Content-Type": "application/json"})
print(response.status_code)
print(response.headers)Custom request
import requests
class RequestUtil:
def __init__(self, base_url):
self.base_url = base_url
def custom_request(self, method, endpoint, data=None, json=None, headers=None):
url = f"{self.base_url}{endpoint}"
response = requests.request(method, url, data=data, json=json, headers=headers)
return response
# Example usage
if __name__ == "__main__":
request_util = RequestUtil("https://api.example.com")
response = request_util.custom_request(
"CUSTOM_METHOD",
"/users",
json={"name": "John Doe", "email": "[email protected]"},
headers={"Content-Type": "application/json"}
)
print(response.status_code)
print(response.json())Advanced techniques
Adding logging
import requests
import logging
class RequestUtil:
def __init__(self, base_url):
self.base_url = base_url
logging.basicConfig(level=logging.INFO)
def get(self, endpoint, params=None, headers=None):
url = f"{self.base_url}{endpoint}"
logging.info(f"Sending GET request to {url}")
response = requests.get(url, params=params, headers=headers)
logging.info(f"Response status code: {response.status_code}")
logging.info(f"Response content: {response.text}")
return response
# Example usage
if __name__ == "__main__":
request_util = RequestUtil("https://api.example.com")
response = request_util.get("/users", params={"page": 1, "limit": 10})
print(response.status_code)
print(response.json())Exception handling
import requests
import logging
class RequestUtil:
def __init__(self, base_url):
self.base_url = base_url
logging.basicConfig(level=logging.INFO)
def get(self, endpoint, params=None, headers=None):
url = f"{self.base_url}{endpoint}"
try:
logging.info(f"Sending GET request to {url}")
response = requests.get(url, params=params, headers=headers)
response.raise_for_status()
logging.info(f"Response status code: {response.status_code}")
logging.info(f"Response content: {response.text}")
return response
except requests.exceptions.RequestException as e:
logging.error(f"Request failed: {e}")
raise
# Example usage
if __name__ == "__main__":
request_util = RequestUtil("https://api.example.com")
try:
response = request_util.get("/users", params={"page": 1, "limit": 10})
print(response.status_code)
print(response.json())
except Exception as e:
print(f"Error: {e}")Session management
import requests
import logging
class RequestUtil:
def __init__(self, base_url):
self.base_url = base_url
self.session = requests.Session()
logging.basicConfig(level=logging.INFO)
def get(self, endpoint, params=None, headers=None):
url = f"{self.base_url}{endpoint}"
logging.info(f"Sending GET request to {url}")
response = self.session.get(url, params=params, headers=headers)
logging.info(f"Response status code: {response.status_code}")
logging.info(f"Response content: {response.text}")
return response
def post(self, endpoint, data=None, json=None, headers=None):
url = f"{self.base_url}{endpoint}"
logging.info(f"Sending POST request to {url}")
response = self.session.post(url, data=data, json=json, headers=headers)
logging.info(f"Response status code: {response.status_code}")
logging.info(f"Response content: {response.text}")
return response
# Example usage (login then reuse token)
if __name__ == "__main__":
request_util = RequestUtil("https://api.example.com")
login_resp = request_util.post(
"/login",
json={"username": "testuser", "password": "testpass"}
)
token = login_resp.json()["token"]
request_util.session.headers.update({"Authorization": f"Bearer {token}"})
users_resp = request_util.get("/users")
print(users_resp.status_code)
print(users_resp.json())Conclusion
By encapsulating HTTP request methods in a reusable RequestUtil class, API test code becomes more concise, maintainable, and robust. The guide covers wrappers for all common verbs, demonstrates logging, error handling, and session reuse, providing a solid foundation for efficient automated testing.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
