A Flask integration test can call routes through Flask’s test client without starting a public server or binding a port. This checks routing, request parsing, response codes, and database behavior together. It builds on the Flask API introduction and the guidance on reliable REST APIs.
Make application setup configurable #
An application factory lets a test pass a separate configuration rather than importing a globally configured server:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(test_config=None):
app = Flask(__name__)
app.config.from_mapping(
SQLALCHEMY_DATABASE_URI="sqlite:///books.db",
SQLALCHEMY_TRACK_MODIFICATIONS=False,
)
if test_config:
app.config.update(test_config)
db.init_app(app)
register_routes(app)
return app
The test can use a temporary SQLite database and create its schema for that test run. For database features that SQLite does not model, use a disposable instance of the same database engine as production.
Send a request through the test client #
import pytest
from app import create_app, db
@pytest.fixture
def client():
app = create_app({
"TESTING": True,
"SQLALCHEMY_DATABASE_URI": "sqlite://",
})
with app.app_context():
db.create_all()
with app.test_client() as client:
yield client
db.session.remove()
db.drop_all()
def test_create_book_returns_created_resource(client):
response = client.post("/books", json={"title": "Dune", "author": "Frank Herbert"})
assert response.status_code == 201
assert response.json["title"] == "Dune"
The exact fixture depends on the project’s ORM and transaction strategy; the important point is that each test gets isolated state and cleans it up.
Check the contract, including invalid requests #
Test the behavior clients rely on: required fields, unsupported content types, missing records, duplicate values, and permission failures. Assert stable status codes and response fields rather than matching an entire response when some values are generated dynamically.
Keep external services outside the test #
Do not use production credentials or call a real payment, email, or third-party API from a normal test run. Inject a fake service or mock only the external boundary. Keep a smaller number of staging or contract tests for integrations that need the real service.
Common isolation mistakes #
- Reusing one database across tests without a reset or rollback.
- Enabling debug mode in a test to reveal exceptions instead of inspecting the captured failure.
- Asserting success while ignoring whether the resource was actually persisted.
- Testing only the happy path and leaving request validation unexamined.
An integration test should be realistic about the Flask route and controlled about the resources surrounding it.