API testing · Python
API automation
Sending requests and checking responses in code (REST Assured or requests).
Learn: API automation in Python with requests and pytest9 minute read
API automation means sending requests from code and checking the responses automatically, so the checks run on every build. Python teams usually do it with two libraries: requests sends the HTTP calls, and pytest finds, runs and reports the tests. Interviewers ask you to write a test for an endpoint, explain how you handle tokens and environments, and show how a failure is debugged. This article builds a small, realistic suite (a group of tests) step by step. It assumes you know basic Python and have seen JSON before (see the HTTP topic).
Sending requests and reading responses
import requests
r = requests.get("https://api.example.com/products",
params={"q": "red shoes", "page": 2}, timeout=10)
r.status_code # 200
r.headers["Content-Type"]
r.json() # the parsed body: a dict for a JSON object
r.text # the raw body as text
r.elapsed # time until the response headers arrived (a timedelta)
Points interviewers look for:
params=builds and encodes the query string; never glue it together by hand.json=sends a JSON body and setsContent-Type: application/json.data=with a dict sends a form-encoded body instead (the format HTML forms use,name=Asha&email=...), a common cause of 400 or 415 errors.- requests has no default timeout (a limit on how long to wait). Without
timeout=, a stuck server can hang a CI job (an automatic test run on a build server) for hours; with it, you get a clearrequests.exceptions.Timeout. - requests does not raise an exception for 4xx or 5xx responses. Assert the status you expect;
raise_for_status()exists but gives less helpful failures in tests.
Shared setup with fixtures
A pytest fixture is a function that prepares something tests need. Put shared fixtures in conftest.py, and pytest passes them to any test that names them as a parameter. A session-scoped fixture runs only once per test run, not once per test. Here it logs in and returns a requests.Session that sends the token on every call and keeps cookies:
# tests/conftest.py
import os
import pytest
import requests
@pytest.fixture(scope="session")
def base_url():
return os.environ.get("BASE_URL", "https://staging.example.com")
@pytest.fixture(scope="session")
def api(base_url):
session = requests.Session()
r = session.post(f"{base_url}/auth/login",
json={"username": os.environ.get("API_USER", "admin"),
"password": os.environ.get("API_PASSWORD", "secret")},
timeout=10)
assert r.status_code == 200, r.text
session.headers.update({"Authorization": f"Bearer {r.json()['token']}"})
yield session
session.close()
The yield line hands the session to the tests. The code after it is teardown (cleanup) and runs when the tests are done, even when tests fail. Credentials come from environment variables, filled from the CI secret store in pipelines; the default values here only suit a local test server.
A test for a POST endpoint
# tests/test_users.py
import uuid
import pytest
import requests
def test_create_user(api, base_url):
email = f"asha-{uuid.uuid4().hex[:8]}@example.com"
r = api.post(f"{base_url}/users", json={"name": "Asha", "email": email}, timeout=10)
assert r.status_code == 201, f"{r.request.method} {r.url} -> {r.status_code}: {r.text}"
created = r.json()
assert created["email"] == email
saved = api.get(f"{base_url}/users/{created['id']}", timeout=10)
assert saved.status_code == 200
assert saved.json()["name"] == "Asha"
@pytest.mark.parametrize("email", ["", "plainaddress", "asha.example.com"])
def test_rejects_invalid_email(api, base_url, email):
r = api.post(f"{base_url}/users", json={"name": "Asha", "email": email}, timeout=10)
assert r.status_code == 400
assert r.json()["field"] == "email"
def test_needs_token(base_url):
r = requests.get(f"{base_url}/users/1", timeout=10)
assert r.status_code == 401
Why this suite is good:
- A unique email per run, so the test can run again and in parallel.
- The assertion message carries the method, URL, status and body, so a CI failure explains itself.
- The GET afterwards proves the user was saved, not only that the API said so.
parametrizeruns the negative test once per value, and each value is reported separately.- The token test deliberately skips the shared session to prove the API rejects anonymous calls (calls with no token).
The line @pytest.mark.parametrize is a decorator, a line placed above a function that changes how it runs. Here it runs the function once for each email in the list.
Checking the response shape
The jsonschema package validates a whole body against a JSON Schema, a description of which fields a body must have and of which types:
from jsonschema import validate
USER_SCHEMA = {"type": "object", "required": ["id", "name", "email"],
"properties": {"id": {"type": "integer"}, "name": {"type": "string"},
"email": {"type": "string"}}}
validate(instance=r.json(), schema=USER_SCHEMA) # raises ValidationError if wrong
Use it for shape (fields, types, allowed values) and plain asserts for the values the test is about.
Other everyday tasks
- Files:
files={"file": open("invoice.pdf", "rb")}sendsmultipart/form-data; use awithblock so the file is closed. - Basic auth:
auth=("user", "password")builds the Basic header. - Response time:
assert r.elapsed.total_seconds() < 2catches big slowdowns, but it is not a load test. - Logging: a response hook on the session, or a small helper, can log every call through the
loggingmodule, and pytest shows captured logs for failing tests. Hide (mask) tokens first. - Typed bodies: small
dataclassmodels (plain classes that only hold fields) withasdict()keep request bodies tidy. This is the Python answer to "do you use POJOs?", a Java term for plain classes that hold data.
Running it
pip install -r requirements.txt # requests, pytest, jsonschema, pinned BASE_URL=https://staging.example.com pytest -m "not slow" --junitxml=reports/api.xml
The first command installs the libraries listed in requirements.txt. In the second, -m "not slow" skips tests marked with @pytest.mark.slow, and --junitxml writes the results as JUnit XML, a common test report format. That file gives the CI server a per-test report, and pytest's exit code (a number returned at the end; 0 only when everything passed) decides whether the pipeline step passes.
How to answer in an interview
Write the POST test above from memory and explain each line: unique data, json=, timeout=, the status with a helpful message, the body checks, the read-back. Then describe the suite: fixtures in conftest.py for the base URL and a logged-in session, parametrize for validation rules, settings and secrets from environment variables, and a JUnit report in CI.
Common mistakes
- Leaving out
timeout=, so one stuck call hangs the whole job. - Sending JSON with
data=instead ofjson=. - Bare asserts with no message, so CI shows only
assert 400 == 201. - Hard-coded passwords and fixed test data that clash on the second run.
Recap
requests sends the calls and pytest runs them. Pass json= and timeout= on every call, assert the exact status with a useful message, read the data back, and keep shared setup in fixtures. A schema checks shape, asserts check values, and the JUnit report plus exit code tell CI the result.
13 questions0/13 solved
| Status | Question | Difficulty | Asked in |
|---|---|---|---|
| Not started | How do you automate API tests in Python? | Easy | Asked in: Capgemini, Coforge, Wipro |
| Not started | requests: json= or data= for a JSON body? | Easy | Asked in: GreyOrange, TCS |
| Pro+ | Reading a requests response | Easy-Medium | Asked in: Capgemini, CloudSEK, Swiggy +1 |
| Pro+ | Why pass timeout= to every requests call? | Easy-Medium | |
| Pro+ | Reuse a token and cookies with requests.Session | Medium | Asked in: Capgemini |
| Pro+ | Write an automated test for a POST endpoint in Python | Medium | Asked in: GreyOrange, TCS |
| Pro+ | Query parameters with requests | Easy-Medium | Asked in: Capgemini, Wipro |
| Pro+ | Validate a response schema in Python | Medium | |
| Pro+ | Fixtures and parametrize in an API suite | Medium | |
| Pro+ | Log requests and responses when a Python API test fails | Medium | |
| Pro+ | Check response time with requests | Medium | |
| Pro+ | Upload a file through an API with requests | Medium | |
| Pro+ | Typed models for request and response bodies in Python | Medium-Hard | Asked in: Accenture, Capgemini |
The first 2 questions of each topic are free. Pro+ opens every question.