Learning Goals
3 min- Create a Flask test client in a fixture.
- Test GET routes: status code and response body.
- Test POST routes: form data, redirects, validation.
- Test session-based behaviour (login required).
Warm-Up · A Fake Browser
5 minfrom myapp import app def test_home(): client = app.test_client() resp = client.get("/") assert resp.status_code == 200 assert b"Welcome" in resp.data
app.test_client() simulates a browser inside your test process: client.get("/path") and client.post(...) return a response with .status_code and .data — no server to start, no port, no network. It exercises your real routes, templates and logic together (an integration test).
New Concept · The Test Client
14 minA client fixture
# conftest.py import pytest from myapp import app @pytest.fixture def client(): app.config["TESTING"] = True # better error propagation return app.test_client()
Test a GET route
def test_home(client): resp = client.get("/") assert resp.status_code == 200 assert b"Welcome" in resp.data # .data is bytes → use b"..." def test_404(client): assert client.get("/nope").status_code == 404
Test a POST with form data
def test_create_post(client): resp = client.post("/new", data={"title": "Hi", "body": "Hello there"}) assert resp.status_code == 302 # redirect after success (PRG) def test_create_validation(client): resp = client.post("/new", data={"title": "", "body": ""}) assert b"required" in resp.data # error shown, no redirect
Follow redirects
resp = client.post("/new", data={...}, follow_redirects=True) assert resp.status_code == 200 assert b"Hi" in resp.data # the new post appears on the page
Test JSON APIs
def test_api(client): resp = client.get("/api/products") assert resp.status_code == 200 data = resp.get_json() # parse JSON response assert len(data) == 3
Test session / login
def test_requires_login(client): assert client.get("/dashboard").status_code == 302 # redirect to login def test_with_session(client): with client.session_transaction() as sess: sess["user_id"] = 1 # fake a logged-in user assert client.get("/dashboard").status_code == 200
Worked Example · Test the L4 Blog
12 min# conftest.py — fresh app + in-memory DB per test import pytest from blog import create_app, init_db @pytest.fixture def client(): app = create_app(database=":memory:") # app factory pattern helps testing app.config["TESTING"] = True with app.app_context(): init_db() return app.test_client()
# test_blog.py def test_home_empty(client): resp = client.get("/") assert resp.status_code == 200 assert b"No posts yet" in resp.data def test_create_and_view(client): # create via POST, follow the redirect to the new post resp = client.post("/new", data={"title": "First Post", "body": "Hello world!"}, follow_redirects=True) assert resp.status_code == 200 assert b"First Post" in resp.data def test_reject_short_title(client): resp = client.post("/new", data={"title": "ab", "body": "x" * 20}) assert b"Title too short" in resp.data def test_view_missing_post_404(client): assert client.get("/post/999").status_code == 404
$ pytest test_blog.py -v test_home_empty PASSED test_create_and_view PASSED test_reject_short_title PASSED test_view_missing_post_404 PASSED 4 passed ← whole web app tested, no server, no browser
Read the diff
These tests drive the real routes, real templates, and a real (in-memory) database — a true integration test of the whole app — yet run in milliseconds with no server. The app-factory pattern (create_app(database=...)) makes it easy to spin a fresh, isolated app per test. This is exactly how production Flask apps are tested.
Try It Yourself
13 minMake a tiny Flask app and test its home route returns 200 and expected content.
Test a POST route: success redirects (302), and invalid input re-renders with an error. Use follow_redirects=True for the happy path.
Test that a protected route redirects when logged out, and works when you set session["user_id"] via session_transaction.
Mini-Challenge · Full CRUD Coverage
8 minFor the L4 blog (or any CRUD app), write test-client tests covering: list (empty + with data), create (success + validation error), view (exists + 404), edit, and delete. Use an in-memory DB and the app-factory so each test is isolated.
Recap
3 minapp.test_client() sends GET/POST in-process — assert on status_code, .data (bytes), and get_json(). Use follow_redirects=True for PRG flows and session_transaction to fake login. An app factory + in-memory DB gives a fresh isolated app per test. This tests the whole web stack without a browser. Next: the real-DB-vs-mock trade-off in depth.
Vocabulary Card
- test client
- Flask's in-process simulated browser for testing routes.
- resp.data / get_json()
- The response body as bytes / parsed JSON.
- follow_redirects
- Make the client follow a redirect so you can assert the final page.
- app factory
- A
create_app()function that builds a fresh, configurable app — ideal for tests.
Homework
4 minTake a Flask app from Level 4/5 (blog, chat, or API). Write a test-client suite covering each route's happy path, an error/validation path, and any login-protected route. Use an in-memory DB. Run coverage and aim high on the routes.
Model on test_blog.py. If your app isn't an app factory yet, refactor it into one — it makes testing dramatically easier.