Tests let you change routes, templates and queries with confidence, and Flask makes them fast and server-free.
Flask apps are easy to test because app.test_client() sends requests straight into the app with no network. With an application factory, the usual pytest setup is three fixtures:
@pytest.fixture
def app():
app = create_app({"TESTING": True, "SQLALCHEMY_DATABASE_URI": "sqlite://"})
yield app
@pytest.fixture
def client(app):
return app.test_client()TESTING=True makes exceptions propagate to the test instead of becoming 500 pages, so you see the real traceback.
The client's methods mirror HTTP verbs: client.get(url, query_string={...}), client.post(url, data={...}) for forms, json={...} for JSON, headers=..., and follow_redirects=True. The response has status_code, headers, text, data (bytes) and get_json().
The client keeps cookies, so sessions work across calls: log in once, then request protected pages. client.session_transaction() lets you set session values directly to skip a login flow.
Using with client: keeps the last request's context alive after the call, so you can assert on session or g afterwards.
For code that needs an app context but not a request (models, services), use with app.app_context():. For CLI commands, app.test_cli_runner().invoke(args=["init-db"]).
Test behavior through HTTP where it matters (status codes, redirects, JSON shape) and keep business logic in plain functions you can test without Flask at all.
Example
import pytest
from flask import Flask, session
def create_app(config=None):
app = Flask(__name__)
app.config.update(SECRET_KEY="test", **(config or {}))
@app.post("/login")
def login():
session["user"] = "ann"
return {"ok": True}
@app.get("/me")
def me():
return ({"user": session["user"]}, 200) if "user" in session else ({}, 401)
return app
@pytest.fixture
def client():
return create_app({"TESTING": True}).test_client()
def test_me_requires_login(client):
assert client.get("/me").status_code == 401
def test_login_then_me(client):
client.post("/login")
assert client.get("/me").get_json() == {"user": "ann"}
if __name__ == "__main__":
print(pytest.main(["-q", __file__])) # 2 passed ... then 0 (ExitCode.OK)Remember
- Build the app in a fixture with TESTING=True and test config
- client.get/post(..., data=, json=, follow_redirects=) drive the app in-process
- The client keeps cookies, so login flows can be tested end to end
Watch out for
- Sharing one app or database across tests, so tests depend on each other's leftovers.