Difficulty: Advanced
Flask provides a built-in test client that lets you make HTTP requests to your application without running a real server. This is the foundation of testing Flask applications. The test client simulates requests, and you assert on the responses to verify your application behaves correctly.
You create a test client from your Flask application with app.test_client(). The client provides methods for every HTTP verb: client.get(), client.post(), client.put(), client.delete(), client.patch(). These methods accept the URL path, data (for form data), json (for JSON body), headers, and other parameters.
The response object from the test client contains status_code, data (raw bytes), json (parsed JSON), headers, and other properties. You use Python assertions to verify the response matches your expectations.
With the application factory pattern, you create a fresh app with test configuration for each test or test session. Test configuration typically uses an in-memory SQLite database, disables CSRF protection, and sets TESTING=True. The TESTING flag disables error catching, so exceptions propagate to the test instead of returning 500.
Flask's test client does not send actual HTTP requests over the network. It calls the WSGI application directly, making tests fast and reliable. There is no need to start a server, deal with port conflicts, or handle network issues.
The test client maintains cookies between requests within the same client instance, simulating a real browser session. This lets you test login flows: POST to /login, then GET a protected page and verify access.
For testing with application context, use app.test_request_context() to push a fake request context. Inside this context, you can use request, current_app, and other context-dependent objects. Use app.app_context() when you only need the application context (for database operations without a request).
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'Hello, World!'
@app.route('/api/status')
def status():
return {'status': 'ok', 'version': '1.0'}
# Testing
def test_index():
client = app.test_client()
response = client.get('/')
assert response.status_code == 200
assert b'Hello, World!' in response.data
def test_status():
client = app.test_client()
response = client.get('/api/status')
assert response.status_code == 200
data = response.get_json()
assert data['status'] == 'ok'
assert data['version'] == '1.0'
def test_not_found():
client = app.test_client()
response = client.get('/nonexistent')
assert response.status_code == 404
Create a test client with app.test_client(). Make requests with client.get(), client.post(), etc. Assert on status_code, data (bytes), and get_json() (parsed JSON).
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.post('/api/users')
def create_user():
data = request.get_json()
if not data or 'name' not in data:
return jsonify({'error': 'Name required'}), 400
return jsonify({'id': 1, 'name': data['name']}), 201
# Testing
def test_create_user_success():
client = app.test_client()
response = client.post('/api/users',
json={'name': 'Alice'},
content_type='application/json'
)
assert response.status_code == 201
assert response.get_json()['name'] == 'Alice'
def test_create_user_missing_name():
client = app.test_client()
response = client.post('/api/users',
json={},
content_type='application/json'
)
assert response.status_code == 400
assert 'error' in response.get_json()
def test_create_user_form_data():
client = app.test_client()
response = client.post('/api/users',
data={'name': 'Bob'} # Form data instead of JSON
)
# This would fail because the view expects JSON
assert response.status_code == 400
Use json= parameter for JSON requests and data= for form data. Test both success and error cases. Verify status codes and response body content.
from flask import Flask, session, jsonify
app = Flask(__name__)
app.config['SECRET_KEY'] = 'test-secret'
@app.post('/login')
def login():
data = request.get_json()
if data.get('password') == 'secret':
session['user'] = data['username']
return jsonify({'message': 'Logged in'})
return jsonify({'error': 'Invalid'}), 401
@app.get('/protected')
def protected():
if 'user' not in session:
return jsonify({'error': 'Unauthorized'}), 401
return jsonify({'message': f'Hello, {session["user"]}'})
# Testing
def test_login_and_access():
client = app.test_client()
# Login
response = client.post('/login', json={
'username': 'alice', 'password': 'secret'
})
assert response.status_code == 200
# Access protected route (session cookie is maintained)
response = client.get('/protected')
assert response.status_code == 200
assert response.get_json()['message'] == 'Hello, alice'
def test_protected_without_login():
client = app.test_client()
response = client.get('/protected')
assert response.status_code == 401
The test client maintains cookies between requests. After logging in, subsequent requests include the session cookie. Create a new client instance for a fresh session.
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.get('/api/data')
def get_data():
auth = request.headers.get('Authorization', '')
if not auth.startswith('Bearer '):
return jsonify({'error': 'Unauthorized'}), 401
token = auth[7:]
if token != 'valid-token':
return jsonify({'error': 'Invalid token'}), 401
return jsonify({'data': 'secret info'})
# Testing
def test_with_valid_token():
client = app.test_client()
response = client.get('/api/data',
headers={'Authorization': 'Bearer valid-token'}
)
assert response.status_code == 200
assert response.get_json()['data'] == 'secret info'
def test_without_token():
client = app.test_client()
response = client.get('/api/data')
assert response.status_code == 401
def test_with_invalid_token():
client = app.test_client()
response = client.get('/api/data',
headers={'Authorization': 'Bearer wrong-token'}
)
assert response.status_code == 401
Pass custom headers via the headers dict. Test all authentication scenarios: valid token, missing token, and invalid token. This pattern works for Bearer tokens, API keys, and other header-based auth.
Test Client, app.test_client, Test Requests, Response Assertions, Test Context