pytest API Testing Environment
Enterprise REST API testing with pytest, requests, httpx, Pydantic validation, JSON Schema, Allure reporting, parallel execution, and mocking.
What's Inside
| Tool | Version | Purpose |
|---|---|---|
| Python | 3.x | Runtime |
| pytest | Latest | Test framework with fixtures, markers, parametrize |
| requests | Latest | Synchronous HTTP client |
| httpx | Latest | Async HTTP client (modern alternative to requests) |
| Pydantic | Latest | Response model validation (type-safe) |
| jsonschema | Latest | JSON Schema contract validation |
| pytest-html | Latest | HTML test reports |
| pytest-xdist | Latest | Parallel test execution |
| pytest-ordering | Latest | Test execution ordering |
| pytest-env | Latest | Environment variable management |
| pytest-timeout | Latest | Test timeout enforcement |
| pytest-cov | Latest | Code coverage reporting |
| allure-pytest | Latest | Allure report integration |
| Allure CLI | Latest | Rich HTML test reporting with trends |
| Faker | Latest | Generate realistic test data |
| responses | Latest | Mock requests library calls |
| respx | Latest | Mock httpx calls |
| cerberus | Latest | Schema validation (alternative) |
| python-dotenv | Latest | .env file loading |
| Java JRE | 17 | Required by Allure CLI |
Quick Start
# Run all API tests
api-run
# Run smoke tests only
api-smoke
# Run tests by marker
api-tag crud
# Run with Allure report
api-allure
# Run in parallel (4 workers)
api-parallel 4
# Show all commands
api-help
Helper Commands
Test Execution
| Command | What it does |
|---|---|
api-run [file] |
Run all tests (or specific file) |
api-smoke |
Run @smoke marked tests |
api-regression |
Run @regression marked tests |
api-tag <marker> |
Run tests by marker (crud, auth, schema, performance) |
api-parallel N |
Run with N parallel workers (pytest-xdist) |
Reports
| Command | What it does |
|---|---|
api-allure |
Run tests + generate and serve Allure report |
api-open-report |
Serve existing Allure report on port 9325 |
Utilities
| Command | What it does |
|---|---|
api-help |
Show all available commands |
Project Structure
workspace/
├── tests/
│ ├── api/
│ │ ├── test_get_requests.py # GET endpoint tests
│ │ ├── test_crud.py # POST/PUT/PATCH/DELETE tests
│ │ ├── test_auth.py # Authentication tests
│ │ ├── test_schema_validation.py # JSON Schema + Pydantic validation
│ │ └── test_data_driven.py # Parametrized data-driven tests
│ └── schemas/
│ └── post_schema.json # JSON Schema definitions
├── models/
│ └── post.py # Pydantic response models
├── utils/
│ └── api_client.py # Reusable API client class
├── config/ # Environment configurations
├── conftest.py # Shared fixtures (session, base_url, auth)
├── pytest.ini # pytest configuration + markers
├── .env # Environment variables (BASE_URL, API_TIMEOUT)
└── exercises/ # 8 guided lab exercises
Test Markers
@pytest.mark.smoke # Quick validation tests
@pytest.mark.regression # Full regression suite
@pytest.mark.crud # CRUD operation tests
@pytest.mark.auth # Authentication tests
@pytest.mark.schema # JSON schema validation tests
@pytest.mark.performance # Response time tests
Run by marker:
api-tag smoke # Quick CI checks
api-tag crud # Only CRUD tests
api-tag "smoke or auth" # Combined markers
Fixtures (conftest.py)
The workspace comes with shared fixtures ready to use:
@pytest.fixture(scope="session")
def base_url():
"""Base URL loaded from .env"""
return "https://jsonplaceholder.typicode.com"
@pytest.fixture(scope="session")
def session():
"""Reusable requests.Session with default headers."""
s = requests.Session()
s.headers.update({"Content-Type": "application/json"})
return s
@pytest.fixture
def auth_headers():
"""Bearer token headers (replace with real auth flow)."""
return {"Authorization": "Bearer test-token-123"}
Writing API Tests
GET Request
import pytest
from models.post import Post
@pytest.mark.smoke
def test_get_posts(session, base_url):
resp = session.get(f"{base_url}/posts")
assert resp.status_code == 200
data = resp.json()
assert len(data) > 0
# Validate with Pydantic
post = Post(**data[0])
assert post.userId > 0
POST Request (Create)
@pytest.mark.crud
def test_create_post(session, base_url):
payload = {"title": "New Post", "body": "Content", "userId": 1}
resp = session.post(f"{base_url}/posts", json=payload)
assert resp.status_code == 201
assert resp.json()["title"] == "New Post"
Data-Driven (Parametrize)
@pytest.mark.parametrize("user_id", [1, 2, 3, 4, 5])
def test_posts_by_user(session, base_url, user_id):
resp = session.get(f"{base_url}/posts", params={"userId": user_id})
assert resp.status_code == 200
assert all(p["userId"] == user_id for p in resp.json())
JSON Schema Validation
import jsonschema
POST_SCHEMA = {
"type": "object",
"required": ["userId", "id", "title", "body"],
"properties": {
"userId": {"type": "integer"},
"id": {"type": "integer"},
"title": {"type": "string", "minLength": 1},
"body": {"type": "string", "minLength": 1},
},
}
@pytest.mark.schema
def test_post_schema(session, base_url):
resp = session.get(f"{base_url}/posts/1")
jsonschema.validate(instance=resp.json(), schema=POST_SCHEMA)
Pydantic Model Validation
from pydantic import BaseModel, Field
class Post(BaseModel):
userId: int
id: int
title: str = Field(min_length=1)
body: str = Field(min_length=1)
def test_response_model(session, base_url):
resp = session.get(f"{base_url}/posts/1")
post = Post(**resp.json()) # Raises ValidationError if invalid
assert post.id == 1
Mocking with responses
import responses
@responses.activate
def test_mocked_api():
responses.add(responses.GET, "https://api.example.com/users",
json=[{"id": 1, "name": "Test"}], status=200)
resp = requests.get("https://api.example.com/users")
assert resp.status_code == 200
Performance Testing
@pytest.mark.performance
def test_response_time(session, base_url):
resp = session.get(f"{base_url}/posts")
assert resp.elapsed.total_seconds() < 2.0
Reusable API Client
The utils/api_client.py provides a production-grade client:
from utils.api_client import APIClient
def test_with_client():
client = APIClient(token="my-token")
resp = client.get("/posts")
assert resp.status_code == 200
Features: base URL management, auth headers, extensible for retry logic and logging.
Async Testing (httpx)
For concurrent API testing:
import httpx
import pytest
@pytest.mark.asyncio
async def test_concurrent_requests():
async with httpx.AsyncClient(base_url="https://jsonplaceholder.typicode.com") as client:
responses = await asyncio.gather(
client.get("/posts/1"),
client.get("/posts/2"),
client.get("/posts/3"),
)
assert all(r.status_code == 200 for r in responses)
Parallel Execution
Speed up large test suites:
api-parallel 4 # 4 workers
api-parallel 8 # 8 workers (for large suites)
Uses pytest-xdist to distribute tests across CPU cores.
Allure Reporting
api-allure
# Generates rich HTML report served on port 9325
Enhance reports with decorators:
import allure
@allure.epic("REST API")
@allure.feature("Posts")
@allure.story("Create post")
@allure.severity(allure.severity_level.CRITICAL)
def test_create_post(session, base_url):
...
allure.attach(resp.text, "Response", allure.attachment_type.JSON)
Environment Configuration
# .env (loaded automatically by conftest.py)
BASE_URL=https://jsonplaceholder.typicode.com
API_TIMEOUT=30
Override at runtime:
BASE_URL=https://staging.myapi.com api-run
Exercises
8 guided lab exercises included:
- pytest Fundamentals — fixtures, markers, assertions
- CRUD Operations — POST/PUT/PATCH/DELETE lifecycle
- Data-Driven Testing — @parametrize with multiple datasets
- JSON Schema Validation — contract testing with jsonschema + Pydantic
- Mocking — responses/respx for isolated testing
- API Client Pattern — reusable client with retry and logging
- Allure Reporting — decorators, attachments, severity levels
- CI/CD Integration — parallel execution, coverage, pipeline configs
VS Code Extensions (Pre-installed)
| Extension | Purpose |
|---|---|
| Python | Language support, debugging, testing |
Keyboard shortcuts:
Ctrl+Shift+B— Run tests + Allure report (default build task)Ctrl+Shift+T— Run all API tests (default test task)
Tips
- Use
api-run -k "test_create"to run tests matching a name pattern - Use
api-run --lfto re-run only last-failed tests - Use
api-run --cov=. --cov-report=htmlfor coverage reports - Pydantic models catch response schema drift automatically — use them for contract testing
responseslibrary lets you test error handling without hitting real APIs- The session fixture is scoped to
session— one TCP connection for all tests (faster)
Security
- Root access is restricted. Use
sudo apt install <package>for system packages. - pip, python3, npm are whitelisted for sudo access.
- Python virtual environment is at
/opt/learncen-venv/bin/python3.