What Is API Testing? Getting Started with Python pytest & requests

📌 Who This Article Is For

  • QA engineers and developers who want to understand what API testing is
  • Those who want to understand the difference between UI testing and API testing
  • Engineers who want to start automated testing with Python
  • Those who want to learn the basics of pytest and requests from scratch

What You Will Learn

  • The difference between API testing and UI testing, and when to use each
  • How to send HTTP requests using the requests library
  • The basic pattern for running API tests automatically with pytest
  • How to auto-generate HTML reports and use them in your portfolio

👨‍💻 About the Author

Working as a QA engineer handling API test automation with Playwright, Python, and pytest in real-world projects. All code used in this article is publicly available on GitHub and has been verified to work as described.

Do you think API testing sounds difficult? In fact, by combining just two libraries — requests and pytest — you can write production-level API tests.

This article walks you through everything from the basic concepts of API testing to environment setup and actual code. Even if you only have experience with UI testing, by the end of this article you will have the confidence to say “I can write API tests too.”


00. What Is API Testing?

API testing is a way to verify that the underlying logic (API) of an application is working correctly.

While UI testing operates a browser to check the screen, API testing sends HTTP requests directly and verifies the responses — no browser required.

UI Testing API Testing
What it checks Screen, appearance, user interaction Data, logic, security
Browser Required Not required
Speed Slow 🐢 Fast ⚡
Fragility Breaks when UI changes Stable as long as logic doesn’t change

💡 Key Takeaway:UI testing and API testing are not about which is better — they serve different purposes. Being able to do both significantly increases your value as a QA engineer.


01. Why Is API Testing Important?

Nearly all modern web apps use a structure where the frontend (UI) and backend (API) are separated.

If the API breaks, even if the screen looks perfectly fine, data won’t be saved, login will fail, and payments will not go through — all critical problems.

API tests also run 10 to 100 times faster than UI tests, making them ideal for integration into CI/CD pipelines to run automatically before every deployment.

# What API testing looks like
# No browser! Communicate with the server using code alone.

import requests

response = requests.get("https://jsonplaceholder.typicode.com/users/1")
print(response.status_code)  # 200
print(response.json())       # {"id": 1, "name": "Leanne Graham", ...}

💡 Key Takeaway:Just 3 lines of code to send a request to an API and check the response. This is the foundation of API testing.


02. Environment Setup

Installing the Required Libraries

Just install these three libraries and you are ready to go.

pip install requests pytest pytest-html
Library Role
requests Send HTTP requests to the API
pytest Run and manage tests
pytest-html Auto-generate HTML reports

pytest.ini Configuration

Place pytest.ini in your project root so that an HTML report is automatically generated every time you run tests.

# pytest.ini
[pytest]
addopts = --html=report.html --self-contained-html

💡 Tip:Once configured, running pytest test_api.py -v alone will automatically generate an HTML report every time. Great for portfolio evidence too.


03. Basics of the requests Library

Using HTTP Methods

Methods are available for each HTTP verb.

import requests

# GET: Retrieve data
response = requests.get("https://jsonplaceholder.typicode.com/users/1")

# POST: Create data
response = requests.post(
    "https://jsonplaceholder.typicode.com/users",
    json={"name": "Taro", "email": "taro@example.com"}
)

# PUT: Update data
response = requests.put(
    "https://jsonplaceholder.typicode.com/users/1",
    json={"name": "Updated Taro"}
)

# DELETE: Delete data
response = requests.delete("https://jsonplaceholder.typicode.com/users/1")

Checking the Response

response = requests.get("https://jsonplaceholder.typicode.com/users/1")

print(response.status_code)  # Status code: 200
print(response.json())       # Response body (JSON format)
print(response.headers)      # Response headers

💡 Key Takeaway:Using response.json() returns the response JSON as a Python dictionary, which you can use directly in assertions.


04. Basics of pytest

Writing Tests

Simply start a function name with test_ and pytest will automatically recognize it as a test.

import requests

def test_get_user():
    """Verify that user information can be retrieved correctly"""
    response = requests.get("https://jsonplaceholder.typicode.com/users/1")

    assert response.status_code == 200, f"Expected: 200, Got: {response.status_code}"
    assert response.json()["id"] == 1
    assert "name" in response.json()

How to Run Tests

# Run all tests
pytest

# Run with verbose output
pytest -v

# Run a specific file only
pytest test_api.py -v

Example Output

test_api.py::test_get_user       PASSED  [ 33%]
test_api.py::test_create_user    PASSED  [ 66%]
test_api.py::test_delete_user    PASSED  [100%]

3 passed in 1.23s ✅

💡 Key Takeaway:When an assert fails, pytest automatically shows what the expected value was and what was actually returned, making debugging much easier.


05. Combining pytest + requests

Combining these two lets you write production-level API tests. Let’s try it out using JSONPlaceholder, a free mock API.

import requests
import pytest

BASE_URL = "https://jsonplaceholder.typicode.com"

def test_get_all_users():
    """Verify that all users can be retrieved"""
    response = requests.get(f"{BASE_URL}/users")

    assert response.status_code == 200
    assert len(response.json()) == 10  # Should return 10 users

def test_create_user():
    """Verify that a user can be created"""
    new_user = {"name": "Yoshitsugu", "email": "test@example.com"}
    response = requests.post(f"{BASE_URL}/users", json=new_user)

    assert response.status_code == 201
    assert response.json()["name"] == "Yoshitsugu"

def test_delete_user():
    """Verify that a user can be deleted"""
    response = requests.delete(f"{BASE_URL}/users/1")

    assert response.status_code == 200

⚠️ Note:JSONPlaceholder is a mock API, so data is not actually saved or deleted. It is safe to use for learning purposes.


06. Pitfalls & Lessons Learned

Here are the key issues I encountered during implementation. I hope this helps others who run into the same problems.


① Confusing requests with pytest-requests

When I first searched for “a library to send HTTP requests with pytest,” I found something called pytest-requests and installed it by mistake. In reality, you use two separate librariesrequests (HTTP client) and pytest (testing framework) — in combination.

# ❌ pytest-requests is not what we need here
pip install pytest-requests

# ✅ Correct installation
pip install requests pytest pytest-html

💡 Key Takeaway:requests is for HTTP requests, pytest is for running tests. They serve different roles, so install and use both.


② Getting a KeyError from response.json()

When trying to extract a value from the response JSON, I specified a key that didn’t exist and got a KeyError. It’s important to check the response content before accessing keys.

# ❌ KeyError if the key doesn't exist
user_id = response.json()["userId"]  # KeyError!

# ✅ First check the content with print()
print(response.json())
# {'id': 1, 'name': 'Leanne Graham', ...}

# ✅ Use get() to avoid KeyError
user_id = response.json().get("id")  # Returns None instead of raising an error

💡 Key Takeaway:For any unfamiliar API, always use print(response.json()) to check the response structure before writing assertions.


③ Hard to Tell Why an assert Failed

At first I only wrote assert response.status_code == 200, so when it failed I had no idea what was actually returned, making debugging slow.

# ❌ Little information when it fails
assert response.status_code == 200

# ✅ Add a message to understand the cause immediately
assert response.status_code == 200, f"Expected: 200, Got: {response.status_code}"

💡 Key Takeaway:Using the assert condition, "message" format lets you see what was expected vs. what was actually returned the moment a test fails.


④ Didn’t Know pytest’s Naming Rules

pytest failed to detect my tests automatically. The cause was that my file names and function names didn’t follow pytest’s naming conventions.

# ❌ pytest won't detect these
# Filename: api_test.py (doesn't start with test_)
def check_user():  # doesn't start with test_
    assert True

# ✅ pytest detects these automatically
# Filename: test_api.py (starts with test_)
def test_check_user():  # starts with test_
    assert True

⚠️ Note:File names must start with test_ or end with _test, and function names must start with test_. These are the two basic rules of pytest.


⑤ Double Slash in URL Due to Trailing Slash in BASE_URL

When I added a trailing / to BASE_URL and then concatenated a path, the URL became //users with a double slash, causing requests to fail.

# ❌ Results in a double slash
BASE_URL = "https://jsonplaceholder.typicode.com/"
response = requests.get(f"{BASE_URL}/users")
# → https://jsonplaceholder.typicode.com//users (WRONG)

# ✅ Remove the trailing slash from BASE_URL
BASE_URL = "https://jsonplaceholder.typicode.com"
response = requests.get(f"{BASE_URL}/users")
# → https://jsonplaceholder.typicode.com/users (CORRECT)

💡 Key Takeaway:Avoid trailing slashes in BASE_URL and consistently add a leading / to each path to prevent confusion.


07. Frequently Asked Questions (FAQ)

Q. What are the benefits of using pytest for API testing?
A. The biggest advantage is that you can write tests intuitively in Python and integrate them easily with CI/CD pipelines. You can also use pytest-html to auto-generate HTML reports, which significantly reduces the effort of creating test evidence.

Q. Which is better — requests or httpx?
A. It depends on your use case. Use requests for simple synchronous processing, and httpx if you need asynchronous processing (async/await). For API testing beginners, requests is simpler and recommended.

Q. Should I start with UI testing or API testing?
A. Starting with API testing is recommended. It runs faster and the environment setup is simpler, making it ideal for learning the basics of test automation. Even if you are already familiar with UI testing, adding API tests helps you stand out as an engineer who can handle both.

Q. What if I don’t have an API to test against?
A. Use publicly available practice APIs. JSONPlaceholder (user/post CRUD) and Restful Booker (authentication flow) are ideal for portfolio API testing. Both are free to use.

Q. What is a pytest fixture?
A. A fixture is a mechanism for sharing setup and teardown logic across tests. For example, if you write token acquisition in a fixture, you can reuse it across multiple test cases. It is an important feature for reducing code duplication and keeping your test structure maintainable.


08. Summary

This article covered the basic concepts of API testing and how to use pytest and requests.

Key Point Details
What is API testing Testing that sends HTTP requests without a browser and verifies responses
Role of requests Sending HTTP requests to the API and receiving responses
Role of pytest Running and managing tests, generating HTML reports
Strength of the combination Achieve production-level API testing with simple, readable code

Even if you only have UI testing experience, learning just requests and pytest is enough to start writing API tests. Publishing your code on GitHub lets you say “I can do both UI and API testing!” — a powerful way to stand out on Upwork and LinkedIn.

タイトルとURLをコピーしました