- Published at
Pytest for API Testing: A Practical Guide
A comprehensive guide on using Pytest for API testing, including setup, test cases, fixtures, and examples.
- Authors
-
-
- Name
- James Lau
- Indie App Developer at Self-employed
-
Table of Contents
- Prerequisites
- Installation
- Configuration
- Test Setup (test_server.py)
- Writing Test Cases
- 1. Test Firebase Authentication
- 2. Test Account Existence
- 3. Test Editing an Account
- 4. Test Signup and Delete User
- 5. Test Brand Rating and Following
- 6. Test Getting Various API Endpoints
- 7. Test Brand Review Workflow
- 8. Test Game API
- 9. Test Vendor APIs
- 10. Test Payment Flow
- Running Tests
- Conclusion
This blog post provides a comprehensive guide on using Pytest for API testing, complete with practical examples. We’ll cover setting up Pytest, writing test cases, using fixtures for authentication, and testing various API endpoints.
Prerequisites
Before diving in, ensure you have the following:
- Python 3.6+
pippackage installer
Installation
Install Pytest and the pytest-django plugin using pip:
pip install pytest pytest-django
Configuration
Create a pytest.ini file in your project’s root directory to configure Pytest. This file tells Pytest how to find your Django settings and test files.
[pytest]
DJANGO_SETTINGS_MODULE = myapp.settings
# -- recommended but optional:
python_files = tests.py test_*.py *_tests.py
DJANGO_SETTINGS_MODULE: Specifies the Django settings module.python_files: Defines the naming convention for test files.
Test Setup (test_server.py)
Let’s create a test_server.py file containing our test cases. We’ll use the requests library to make HTTP requests and Faker to generate fake data.
import pytest
import requests
import json
from faker import Faker
import sys
import os
# Add the parent directory to the Python path
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from utils import get_fb_test_account_token
# Constants
BASE_URL = "https://your-web-app.net" # You can change this or make it configurable
fake = Faker()
@pytest.fixture(scope="session")
def auth_token():
# This function will be called once per test session
# Implement your logic to get the auth token here
# For example:
auth_token = get_fb_test_account_token()
print(f"auth_token {auth_token}")
return auth_token
@pytest.fixture(scope="session")
def vendor_token():
return "vendor-token"
def get_api_creator(BASE_URL, api, token):
url = f"{BASE_URL}/{api}"
headers = {"accept": "*/*", "Authorization": token}
response = requests.get(url, headers=headers)
return response
Explanation:
- Imports: We import necessary libraries like
pytest,requests,json, andFaker. sys.path.append: This line is crucial if yourutilsmodule (containing helper functions likeget_fb_test_account_token) resides in a parent directory. It modifies the Python path to include the parent directory, allowing the import to succeed. Adjust the path accordingly for your project structure.BASE_URL: This constant stores the base URL of your API. It’s good practice to define such constants for easy modification.auth_tokenfixture: This fixture is decorated with@pytest.fixture(scope="session"). This means theauth_tokenfixture will only be called once per test session. Fixtures are a powerful way to manage dependencies and setup/teardown resources for your tests. In this example, it retrieves an authentication token using theget_fb_test_account_token()function (implementation not shown but assumed to retrieve a token, perhaps from a testing service or environment variable). The token is then printed to the console for debugging and returned for use in subsequent tests.vendor_tokenfixture: Similar toauth_token, this fixture provides a token for vendor-specific API calls.get_api_creatorfunction: This helper function simplifies making GET requests to the API. It takes the base URL, API endpoint, and authentication token as input and returns the response.
Writing Test Cases
Here are some example test cases demonstrating various API testing scenarios:
1. Test Firebase Authentication
def test_firebase_auth(auth_token):
response = get_api_creator(BASE_URL, "api/test_firebase_auth", auth_token)
assert response.status_code == 200
data = response.json()
assert "uid" in data
This test case calls an API endpoint (api/test_firebase_auth) that likely verifies Firebase authentication. It asserts that the response status code is 200 (OK) and that the JSON response contains a uid field.
2. Test Account Existence
def test_account_exist(auth_token):
user_id = "test_user_id" # You might want to get this from the firebase_auth test
response = get_api_creator(BASE_URL, f"api/account/exist?userID={user_id}", auth_token)
assert response.status_code == 200
data = response.json()
assert "exist" in data
This test checks if an account exists for a given userID. It calls the api/account/exist endpoint with the userID as a query parameter and asserts that the response contains an exist field.
3. Test Editing an Account
def test_edit_account(auth_token):
name = fake.name()
payload = {
"name": name,
"username": name,
"bio": f"test_{name}",
}
response = requests.post(
f"{BASE_URL}/api/account/edit",
headers={"accept": "*/*", "Authorization": auth_token},
files={'payload': (None, json.dumps(payload), 'application/json')}
)
assert response.status_code == 200
# Verify the change
response = get_api_creator(BASE_URL, "api/account", auth_token)
assert response.status_code == 200
data = response.json()
assert data["name"] == name
This test demonstrates how to use requests.post to send data to the API. It uses the Faker library to generate a random name and then sends a POST request to the api/account/edit endpoint to update the account information. Note the use of files parameter to send the JSON payload. Finally, it verifies that the name has been updated correctly by making a GET request to the api/account endpoint and checking the name field in the response.
4. Test Signup and Delete User
@pytest.fixture(scope="module")
def test_user_id():
return "abcdefg9527952795279527"
@pytest.mark.order(1)
def test_signup(test_user_id):
signup_param = {
"name": "Alex Xiao2",
"username": "Alex Xiao",
"googleEmail": "9527@abcdefg.live",
"userID": test_user_id,
}
response = requests.post(f"{BASE_URL}/api/signup", json=signup_param)
assert response.status_code == 200
@pytest.mark.order(2)
def test_delete_user(auth_token):
response = requests.delete(
f"{BASE_URL}/api/account",
headers={"accept": "*/*", "Authorization": auth_token}
)
assert response.status_code == 200
This example shows a signup and delete flow. test_user_id fixture provides a user ID. The @pytest.mark.order decorator can be used to specify the execution order of tests. In this case, test_signup is executed before test_delete_user. The test_signup function sends a POST request to the api/signup endpoint with signup parameters. The test_delete_user function sends a DELETE request to the api/account endpoint to delete the user.
5. Test Brand Rating and Following
def test_brand_rate_and_follow(auth_token):
# Rate a brand
response = requests.post(
f"{BASE_URL}/api/brand/rate",
headers={"accept": "*/*", "Authorization": auth_token},
json={"company_id": 41, "rating": 5}
)
assert response.status_code == 200
# Follow a brand
response = requests.post(
f"{BASE_URL}/api/brand/follow",
headers={"accept": "*/*", "Authorization": auth_token},
json={"company_id": 41}
)
assert response.status_code == 200
# Unfollow a brand
response = requests.delete(
f"{BASE_URL}/api/brand/follow?company_id=41",
headers={"accept": "*/*", "Authorization": auth_token}
)
assert response.status_code == 200
This test case demonstrates testing multiple API calls within a single test function. It rates a brand, follows a brand, and then unfollows the brand, asserting that each request is successful.
6. Test Getting Various API Endpoints
def test_new_build_get_apis(auth_token):
endpoints = [
"api/homepage",
"api/account",
"api/brand/all",
"api/brand/favourites",
"api/brand/detail?company_id=41",
"api/brand/review?company_id=41&order=desc_time&filter=1-3",
"api/notifications",
"api/transactions",
"api/vouchers",
"api/help"
]
for endpoint in endpoints:
response = get_api_creator(BASE_URL, endpoint, auth_token)
assert response.status_code == 200, f"Failed to get {BASE_URL}{endpoint}"
This test iterates through a list of API endpoints and asserts that each endpoint returns a successful response (status code 200). This is a useful test for ensuring that all basic endpoints are functioning correctly.
7. Test Brand Review Workflow
def test_brand_review(auth_token):
# Create a review
payload = {
"company_id": 41,
"rating": 3,
"ambience_rating": 1,
"quality_rating": 1,
"service_rating": 1,
"review_list": [
{"question": "test_q", "answer": "test_a"},
{"question": "test_q", "answer": "test_a"},
]
}
response = requests.post(
f"{BASE_URL}/api/brand/review",
headers={"accept": "*/*", "Authorization": auth_token},
json=payload
)
assert response.status_code == 200
review_data = response.json()
review_id = review_data.get("review_id") # Assuming the API returns the review ID
# Upvote the review
response = requests.post(
f"{BASE_URL}/api/brand/review/upvote?brandreview_id={review_id}",
headers={"accept": "*/*", "Authorization": auth_token}
)
assert response.status_code == 200
# Remove upvote
response = requests.delete(
f"{BASE_URL}/api/brand/review/upvote?brandreview_id={review_id}",
headers={"accept": "*/*", "Authorization": auth_token}
)
assert response.status_code == 200
# Delete the review
response = requests.delete(
f"{BASE_URL}/api/brand/review?review_id={review_id}",
headers={"accept": "*/*", "Authorization": auth_token}
)
assert response.status_code == 200
This test case tests the complete flow of creating, upvoting, and deleting a brand review.
8. Test Game API
def test_game_api(auth_token):
# Initialize game
response = requests.post(
f"{BASE_URL}/api/game/initialize?discount_cnt=6&wheel_game=false",
headers={"accept": "*/*", "Authorization": auth_token}
)
assert response.status_code == 200
game_data = response.json()
game_id = game_data["game_id"]
# Finalize game
data = {
"game_id": game_id,
"status_array": [False, False, False, False, False, False]
}
response = requests.post(
f"{BASE_URL}/api/game/finalize",
headers={"accept": "*/*", "Authorization": auth_token},
json=data
)
assert response.status_code == 200
This test case initializes and finalizes a game, ensuring that the API endpoints for game management are working correctly.
9. Test Vendor APIs
def test_vendor_apis(vendor_token):
endpoints = [
"api/vendor/transactions",
"api/vendor/polls",
"api/vendor/help"
]
for endpoint in endpoints:
response = get_api_creator(BASE_URL, endpoint, vendor_token)
assert response.status_code == 200
This test case checks vendor-specific API endpoints using the vendor_token fixture.
10. Test Payment Flow
def test_payment_flow(auth_token, vendor_token):
# Generate QR code
response = requests.post(
f"{BASE_URL}/api/vendor/generate_payment_qrcode",
headers={"accept": "*/*", "Authorization": vendor_token},
json={"total_amount": 100}
)
assert response.status_code == 200
qr_data = response.json()
transaction_id = qr_data["transaction_id"]
# Refresh QR code
response = requests.post(
f"{BASE_URL}/api/vendor/refresh_payment_qrcode?transaction_id={transaction_id}",
headers={"accept": "*/*", "Authorization": vendor_token}
)
assert response.status_code == 200
refresh_data = response.json()
qr_code_token = refresh_data["token"]
# Get transaction (vendor side)
response = get_api_creator(BASE_URL, f"api/vendor/get-transaction?transaction_id={transaction_id}", vendor_token)
assert response.status_code == 200
assert response.json()["status"] == "AVAILABLE"
# Get user payment method
response = get_api_creator(BASE_URL, "api/stripe/paymentmethod", auth_token)
assert response.status_code == 200
# Scan QR code
response = get_api_creator(BASE_URL, f"api/stripe/scan-transaction?token={qr_code_token}", auth_token)
assert response.status_code == 200
# Get transaction (user side)
response = get_api_creator(BASE_URL, f"api/stripe/get-transaction?transaction_id={transaction_id}", auth_token)
assert response.status_code == 200
assert response.json()["status"] == "SCANNED"
# Cancel transaction
response = requests.post(
f"{BASE_URL}/api/vendor/cancel-transaction?transaction_id={transaction_id}",
headers={"accept": "*/*", "Authorization": vendor_token}
)
assert response.status_code == 200
assert response.json()["msg"] == "success"
This comprehensive test case simulates a payment flow, including generating and refreshing QR codes, retrieving transaction details, and canceling the transaction. It also utilizes both auth_token and vendor_token fixtures.
Running Tests
To run the tests, navigate to your project’s root directory in the terminal and run:
pytest
Pytest will discover and execute all test functions in your test files, providing detailed output on the test results.
Conclusion
This blog post demonstrated how to use Pytest to create robust and comprehensive API tests. By utilizing fixtures, helper functions, and a clear test structure, you can ensure the reliability and functionality of your APIs.