Flask Testing
Testing is the foundation of ensuring code quality.
Flask provides a built-in test client, which combined with pytest allows for efficient writing and running of tests.
Test Client——test_client
Flask'stest_client()The method creates a mocked HTTP client that can send requests without starting a server:
Example
# File path: test_demo.py (test in Python interactive environment)
from app import create_app
# Create a test app instance
app = create_app()
# Create a test client
with app.test_client() as client:
# Send GET request
response = client.get("/")
print(response.status_code) # 200
print(response.data[:100]) # The first 100 bytes of the response body
# Send POST request and send data as JSON
response = client.post("/api/posts",
json={"title": "EXAMPLE Tutorial", "body": "content", "author_id": 1})
print(response.status_code) # 201
print(response.get_json()) # Parse JSON response
from app import create_app
# Create a test app instance
app = create_app()
# Create a test client
with app.test_client() as client:
# Send GET request
response = client.get("/")
print(response.status_code) # 200
print(response.data[:100]) # The first 100 bytes of the response body
# Send POST request and send data as JSON
response = client.post("/api/posts",
json={"title": "EXAMPLE Tutorial", "body": "content", "author_id": 1})
print(response.status_code) # 201
print(response.get_json()) # Parse JSON response
pytest Integration
pytest is the standard testing framework in the Python community and works very conveniently with Flask.
Install pytest
(.venv) $ pip install pytest
Writing Tests——conftest.py (Shared Fixtures)
Usageconftest.pyThe file creates reusable test fixtures:
Example
# File path: tests/conftest.py
import os
import tempfile
import pytest
from app import create_app
from db import init_db, get_db
@pytest.fixture
def app():
"""Create a test app instance using a temporary database"""
# Create a temporary file as test database
db_fd, db_path = tempfile.mkstemp()
# Create app, override database path to temporary file
app = create_app()
app.config["DATABASE"] = db_path
app.config["TESTING"] = True # Enable test mode
# Initialize test database table structures
with app.app_context():
init_db()
yield app
# Clean up after tests: close and delete the temporary database file
os.close(db_fd)
os.unlink(db_path)
@pytest.fixture
def client(app):
"""Create test client"""
return app.test_client()
@pytest.fixture
def runner(app):
"""Create CLI test runner"""
return app.test_cli_runner()
import os
import tempfile
import pytest
from app import create_app
from db import init_db, get_db
@pytest.fixture
def app():
"""Create a test app instance using a temporary database"""
# Create a temporary file as test database
db_fd, db_path = tempfile.mkstemp()
# Create app, override database path to temporary file
app = create_app()
app.config["DATABASE"] = db_path
app.config["TESTING"] = True # Enable test mode
# Initialize test database table structures
with app.app_context():
init_db()
yield app
# Clean up after tests: close and delete the temporary database file
os.close(db_fd)
os.unlink(db_path)
@pytest.fixture
def client(app):
"""Create test client"""
return app.test_client()
@pytest.fixture
def runner(app):
"""Create CLI test runner"""
return app.test_cli_runner()
Test Routes
Example
# File path: tests/test_app.py
def test_home_page(client):
"""Test if the homepage returns normally"""
response = client.get("/")
assert response.status_code == 200
# Check if the page contains expected keywords
assert b"EXAMPLE" in response.data
def test_404_page(client):
"""Test visiting a non-existent page"""
response = client.get("/this-page-does-not-exist")
assert response.status_code == 404
def test_home_page(client):
"""Test if the homepage returns normally"""
response = client.get("/")
assert response.status_code == 200
# Check if the page contains expected keywords
assert b"EXAMPLE" in response.data
def test_404_page(client):
"""Test visiting a non-existent page"""
response = client.get("/this-page-does-not-exist")
assert response.status_code == 404
Run tests:
(.venv) $ pytest tests/test_app.py -v ==================================== tests/test_app.py::test_home_page PASSED tests/test_app.py::test_404_page PASSED ====================================
Test JSON API
Example
# File path: tests/test_api.py
def test_create_post(client):
"""Test create article API"""
response = client.post("/api/posts", json={
"title": "EXAMPLE test article",
"body": "This is test content",
"author_id": 1
})
# Successful creation should return 201
assert response.status_code == 201
data = response.get_json()
assert data["title"] == "EXAMPLE test article"
assert "id" in data
def test_create_post_without_title(client):
"""Test that empty title returns 400 error"""
response = client.post("/api/posts", json={
"title": "",
"body": "content",
"author_id": 1
})
assert response.status_code == 400
assert b"error" in response.data
def test_get_posts(client):
"""Test getting article list"""
# First create an article
client.post("/api/posts", json={
"title": "Article A", "body": "Content A", "author_id": 1
})
# Get list
response = client.get("/api/posts")
assert response.status_code == 200
data = response.get_json()
assert len(data) >= 1
def test_delete_post(client):
"""Test delete article"""
# First create
rv = client.post("/api/posts", json={
"title": "To be deleted", "body": "content", "author_id": 1
})
post_id = rv.get_json()["id"]
# Then delete
rv = client.delete(f"/api/posts/{post_id}")
assert rv.status_code == 200
# Confirm deleted
rv = client.get(f"/api/posts/{post_id}")
assert rv.status_code == 404
def test_create_post(client):
"""Test create article API"""
response = client.post("/api/posts", json={
"title": "EXAMPLE test article",
"body": "This is test content",
"author_id": 1
})
# Successful creation should return 201
assert response.status_code == 201
data = response.get_json()
assert data["title"] == "EXAMPLE test article"
assert "id" in data
def test_create_post_without_title(client):
"""Test that empty title returns 400 error"""
response = client.post("/api/posts", json={
"title": "",
"body": "content",
"author_id": 1
})
assert response.status_code == 400
assert b"error" in response.data
def test_get_posts(client):
"""Test getting article list"""
# First create an article
client.post("/api/posts", json={
"title": "Article A", "body": "Content A", "author_id": 1
})
# Get list
response = client.get("/api/posts")
assert response.status_code == 200
data = response.get_json()
assert len(data) >= 1
def test_delete_post(client):
"""Test delete article"""
# First create
rv = client.post("/api/posts", json={
"title": "To be deleted", "body": "content", "author_id": 1
})
post_id = rv.get_json()["id"]
# Then delete
rv = client.delete(f"/api/posts/{post_id}")
assert rv.status_code == 200
# Confirm deleted
rv = client.get(f"/api/posts/{post_id}")
assert rv.status_code == 404
Test Session
Testing features that require Session, such as login:
Example
# File path: tests/test_auth.py
def test_login(client):
"""Test login flow"""
# Send login request
response = client.post("/auth/login", data={
"username": "testuser",
"password": "testpass"
}, follow_redirects=True) # follow_redirects automatically follows redirects
assert response.status_code == 200
def test_session_transaction(client):
"""Directly manipulate session for testing"""
with client.session_transaction() as session:
# Directly set values in session to simulate logged-in state
session["username"] = "example"
# Now visit the home page; it should show logged-in status
response = client.get("/")
assert b"example" in response.data
def test_login(client):
"""Test login flow"""
# Send login request
response = client.post("/auth/login", data={
"username": "testuser",
"password": "testpass"
}, follow_redirects=True) # follow_redirects automatically follows redirects
assert response.status_code == 200
def test_session_transaction(client):
"""Directly manipulate session for testing"""
with client.session_transaction() as session:
# Directly set values in session to simulate logged-in state
session["username"] = "example"
# Now visit the home page; it should show logged-in status
response = client.get("/")
assert b"example" in response.data
client.session_transaction()It is a very practical testing tool — it allows you to directly modify Session content without going through the complete login process.
Testing CLI commands
Example
# File path: tests/test_cli.py
def test_init_db(runner):
"""Test the database initialization command"""
result = runner.invoke(args=["init-db"])
# Command executed successfully; exit_code should be 0
assert result.exit_code == 0
def test_flask_routes(runner):
"""Test the flask routes command"""
result = runner.invoke(args=["routes"])
assert result.exit_code == 0
# Check whether the output contains registered routes
assert "/api/posts" in result.output
def test_init_db(runner):
"""Test the database initialization command"""
result = runner.invoke(args=["init-db"])
# Command executed successfully; exit_code should be 0
assert result.exit_code == 0
def test_flask_routes(runner):
"""Test the flask routes command"""
result = runner.invoke(args=["routes"])
assert result.exit_code == 0
# Check whether the output contains registered routes
assert "/api/posts" in result.output
Testing Best Practices
| Practice | Description |
|---|---|
| Using a Temporary Database | Each test uses an independent database so they do not interfere with one another (e.g., tempfile in conftest.py) |
| Enabling TESTING mode | app.config["TESTING"] = TrueLet exceptions propagate normally rather than being swallowed by error handling |
| One test should only test one thing. | Each test function verifies only one behavior, making it easier to pinpoint issues |
| Clear naming | Function names are liketest_<scenario>_<expected_result> |
| Make good use of follow_redirects | Useful when testing the redirect flow after a POST |
Run All Tests
(.venv) $ pytest tests/ -v tests/test_app.py::test_home_page PASSED tests/test_app.py::test_404_page PASSED tests/test_api.py::test_create_post PASSED tests/test_api.py::test_create_post_without_title PASSED tests/test_api.py::test_get_posts PASSED tests/test_api.py::test_delete_post PASSED tests/test_auth.py::test_login PASSED tests/test_auth.py::test_session_transaction PASSED tests/test_cli.py::test_init_db PASSED tests/test_cli.py::test_flask_routes PASSED ==================== 10 passed in 0.45s ====================other extensions