Sitemap

Create and Test a Mock API Server in Python Using Flask and Pytest (with GitHub Actions CI)

6 min readJul 5, 2025

Have you ever wanted a fast way to mock REST APIs and run automated tests against them — all in Python?

In this guide, we’ll create a mock API server using Flask, write automated tests using Pytest, and run the entire workflow in GitHub Actions CI.

Press enter or click to view image in full size

By the end, you’ll have a fully working mock server with 15+ endpoints, a test suite, and a CI pipeline that validates your API on every push. Let’s dive in!

1. Project Structure

Here’s how our project is organized:

python-api-mock-test/
├── app.py # Flask mock server
├── requirements.txt # Project dependencies
├── test_mock_apis.sh # Optional: curl test script
├── tests/
│ └── test_api.py # Pytest test suite

2. Create the Flask Mock Server (app.py)

This file will define your API endpoints:

import os
import sys

# Ensure the current directory is in the module search path for CI environments
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))

from flask import Flask, jsonify, request

app = Flask(__name__)

@app.before_request
def log_request():
print(f"==> {request.method} {request.path}")

@app.route('/')
def index():
return jsonify({
'message': 'Mock Server Running',
'endpoints': [
'/api/user/<user_id>',
'/api/user [POST, PUT, DELETE]',
'/api/products',
'/api/order/<order_id>',
'/api/order [POST]',
'/api/login',
'/api/logout',
'/api/health',
'/api/notifications',
'/api/settings [GET, PUT]',
'/api/profile [GET, PATCH]',
'/api/reports',
'/api/upload [POST]'
]
})

@app.route('/api/user/<int:user_id>', methods=['GET'])
def get_user(user_id):
return jsonify({'user_id': user_id, 'name': 'User'+str(user_id)})

@app.route('/api/user', methods=['POST'])
def create_user():
data = request.get_json()
return jsonify({'message': 'User created', 'user': data}), 201

@app.route('/api/user/<int:user_id>', methods=['PUT'])
def update_user(user_id):
data = request.get_json()
return jsonify({'message': 'User updated', 'user_id': user_id, 'updates': data})

@app.route('/api/user/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
return jsonify({'message': f'User {user_id} deleted'})

@app.route('/api/products', methods=['GET'])
def get_products():
return jsonify({'products': ['Product A', 'Product B', 'Product C']})

@app.route('/api/order/<int:order_id>', methods=['GET'])
def get_order(order_id):
return jsonify({'order_id': order_id, 'status': 'processing'})

@app.route('/api/order', methods=['POST'])
def create_order():
data = request.get_json()
return jsonify({'message': 'Order placed', 'order': data}), 201

@app.route('/api/login', methods=['POST'])
def login():
creds = request.get_json()
return jsonify({'message': f"Welcome {creds.get('username')}", 'token': 'mock-token-123'})

@app.route('/api/logout', methods=['POST'])
def logout():
return jsonify({'message': 'User logged out'})

@app.route('/api/health', methods=['GET'])
def health_check():
return jsonify({'status': 'ok', 'uptime': '100h'})

@app.route('/api/notifications', methods=['GET'])
def get_notifications():
return jsonify({'notifications': ['Message 1', 'Alert 2', 'Reminder 3']})

@app.route('/api/settings', methods=['GET'])
def get_settings():
return jsonify({'theme': 'dark', 'language': 'en', 'timezone': 'UTC'})

@app.route('/api/settings', methods=['PUT'])
def update_settings():
data = request.get_json()
return jsonify({'message': 'Settings updated', 'settings': data})

@app.route('/api/profile', methods=['GET'])
def get_profile():
return jsonify({'name': 'John Doe', 'email': 'john@example.com', 'role': 'admin'})

@app.route('/api/profile', methods=['PATCH'])
def update_profile():
data = request.get_json()
return jsonify({'message': 'Profile updated', 'updates': data})

@app.route('/api/reports', methods=['GET'])
def get_reports():
return jsonify({'reports': ['Report1.pdf', 'Report2.xlsx']})

@app.route('/api/upload', methods=['POST'])
def upload_file():
return jsonify({'message': 'File uploaded successfully', 'file_id': '12345'})

if __name__ == '__main__':
print("Starting Flask mock server on http://localhost:5000")
app.run(debug=False, port=5000)

3. Install Dependencies

Create your requirements.txt:

Flask==2.3.2
pytest==8.2.1

Then install them in a virtual environment:

python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

4. Write Tests with Pytest

Create tests/test_api.py:



import pytest
from app import app

@pytest.fixture
def client():
with app.test_client() as client:
yield client

def test_get_user(client):
response = client.get('/api/user/1')
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert response.json['user_id'] == 1

def test_create_user(client):
response = client.post('/api/user', json={'name': 'New User'})
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 201
assert response.json['user']['name'] == 'New User'

def test_update_user(client):
response = client.put('/api/user/1', json={'name': 'Updated'})
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert response.json['updates']['name'] == 'Updated'

def test_delete_user(client):
response = client.delete('/api/user/1')
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200

def test_get_products(client):
response = client.get('/api/products')
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert 'products' in response.json

def test_get_order(client):
response = client.get('/api/order/100')
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert response.json['order_id'] == 100

def test_create_order(client):
response = client.post('/api/order', json={'item': 'Book'})
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 201
assert response.json['order']['item'] == 'Book'

def test_login(client):
response = client.post('/api/login', json={'username': 'admin'})
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert 'token' in response.json

def test_logout(client):
response = client.post('/api/logout')
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200

def test_health_check(client):
response = client.get('/api/health')
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert response.json['status'] == 'ok'

def test_get_notifications(client):
response = client.get('/api/notifications')
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert isinstance(response.json['notifications'], list)

def test_get_settings(client):
response = client.get('/api/settings')
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert 'theme' in response.json

def test_update_settings(client):
response = client.put('/api/settings', json={'theme': 'light'})
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert response.json['settings']['theme'] == 'light'

def test_get_profile(client):
response = client.get('/api/profile')
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert response.json['name'] == 'John Doe'

def test_update_profile(client):
response = client.patch('/api/profile', json={'name': 'Jane'})
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert response.json['updates']['name'] == 'Jane'

def test_get_reports(client):
response = client.get('/api/reports')
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert isinstance(response.json['reports'], list)

def test_upload_file(client):
response = client.post('/api/upload')
print("Response Status:", response.status_code)
print("Response JSON:", response.get_json())
assert response.status_code == 200
assert 'file_id' in response.json

Run tests:

pytest

5. Run Mock API Manually

Start the server:

python app.py

Now access it in your browser or Postman at:

GET http://localhost:5000/api/health or GET http://127.0.0.1:5000/api/health

6. Automate with GitHub Actions CI

Create .github/workflows/test.yml:

name: Flask Mock Server Test

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout repo
uses: actions/checkout@v3

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Cache pip dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install dependencies
working-directory: .
run: |
python -m venv venv
source venv/bin/activate
export PYTHONPATH=.
pip install --upgrade pip
pip install -r requirements.txt

- name: Start Flask server in background
working-directory: .
run: |
source venv/bin/activate
export PYTHONPATH=.
nohup python app.py > flask.log 2>&1 &
sleep 5

- name: Run pytest tests
working-directory: .
run: |
source venv/bin/activate
export PYTHONPATH=.
pytest tests

- name: Run curl script (optional)
working-directory: .
run: |
chmod +x test_mock_apis.sh
./test_mock_apis.sh

- name: Show Flask server logs
if: always()
working-directory: .
run: cat flask.log
Press enter or click to view image in full size
Press enter or click to view image in full size

7. Push and Verify

Push your code to GitHub, and the CI workflow will:

✅ Install dependencies

✅ Start your mock server

✅ Run your test suite

✅ Print logs if anything fails

Bonus: Curl Test Script

You can create test_mock_apis.sh:

#!/bin/bash

BASE_URL="http://127.0.0.1:5000"

declare -a endpoints=(
"/api/user/1"
"/api/user"
"/api/user/1" # PUT
"/api/user/1" # DELETE
"/api/products"
"/api/order/100"
"/api/order"
"/api/login"
"/api/logout"
"/api/health"
"/api/notifications"
"/api/settings"
"/api/settings" # PUT
"/api/profile"
"/api/profile" # PATCH
"/api/reports"
"/api/upload"
)

declare -a methods=(
"GET"
"POST"
"PUT"
"DELETE"
"GET"
"GET"
"POST"
"POST"
"POST"
"GET"
"GET"
"GET"
"PUT"
"GET"
"PATCH"
"GET"
"POST"
)

declare -a payloads=(
""
'{"name": "New User"}'
'{"name": "Updated"}'
""
""
""
'{"item": "Book"}'
'{"username": "admin"}'
""
""
""
""
'{"theme": "light"}'
""
'{"name": "Jane"}'
""
""
)

echo "Testing all mock API endpoints..."

for i in "${!endpoints[@]}"; do
method=${methods[$i]}
url="$BASE_URL${endpoints[$i]}"
data=${payloads[$i]}
echo
echo "===== $method $url ====="
if [[ "$method" == "GET" || "$method" == "DELETE" ]]; then
curl -s -X $method "$url" -H "Content-Type: application/json"
else
curl -s -X $method "$url" -H "Content-Type: application/json" -d "$data"
fi
echo
done

Make it executable:

chmod +x test_mock_apis.sh
./test_mock_apis.sh

8. .gitignore

Ignore unnecessary files:

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*.pyo

# Virtual environment
.venv/
venv/
env/

# Editor settings
.vscode/
.idea/

# Test output and logs
flask.log
*.log
*.coverage
.coverage.*

# Pytest cache
.pytest_cache/

# Mac-specific files
.DS_Store

# Bash history or temp files
*.sh~


.idea

Conclusion

You now have:

  • A full Flask-based mock API server
  • A Pytest suite to validate behavior
  • CI integration with GitHub Actions

This setup is ideal for API prototyping, contract testing, mocking microservices, or onboarding frontend teams.

If you enjoyed this, follow me for more on testing, APIs, CI/CD, and dev automation!

I have created a project on GitHub and added the code here.

Feel free to hit clap if you like the content. Happy Automation Testing :) Cheers. 👏

If you’d like to support my work, please leave a good rating for my Chrome plugin here and my Firefox plugin here.

Also, I have created an automation framework using Chrome CDP here.

--

--

Pradap Pandiyan
Pradap Pandiyan

Written by Pradap Pandiyan

I’m a passionate QA Engineer. I’m a motovlogger, content creator, off-roader and freelancer. Buy me a coffee here https://www.buymeacoffee.com/pradappandiyan

No responses yet