Labs ICT
Pro Login

Testing Flask Apps

Writing tests for your routes.

Testing Flask Apps

Testing might feel like extra work, but it's actually the opposite — it saves you time by catching bugs before your users do. Flask makes it easy to test your routes, templates, and logic without starting a real server. You can simulate requests and check responses programmatically.

Flask Test Client

Flask comes with a test client that lets you send requests to your app without running a server. It's fast, isolated, and gives you full control over the test environment.

import pytest
from app import app, db

@pytest.fixture
def client():
    app.config['TESTING'] = True
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'

    with app.test_client() as client:
        with app.app_context():
            db.create_all()
        yield client

def test_home_page(client):
    response = client.get('/')
    assert response.status_code == 200
    assert b'Welcome' in response.data

def test_about_page(client):
    response = client.get('/about')
    assert response.status_code == 200

The test client behaves like a browser — it sends requests and receives responses. The response.status_code tells you if the request succeeded, and response.data contains the body.

Assert Methods

You have several ways to verify that responses are correct. Check status codes, content, headers, and redirects.

def test_login(client):
    # Test successful login
    response = client.post('/login', data={
        'username': 'testuser',
        'password': 'testpass'
    }, follow_redirects=True)
    assert response.status_code == 200
    assert b'Dashboard' in response.data

def test_login_failure(client):
    response = client.post('/login', data={
        'username': 'wronguser',
        'password': 'wrongpass'
    })
    assert response.status_code == 200
    assert b'Invalid credentials' in response.data

def test_redirect(client):
    response = client.get('/dashboard')
    assert response.status_code == 302
    assert '/login' in response.location

setUp and tearDown

You want a clean database for each test. Set up the database before tests and tear it down after.

class TestRoutes:
    def setup_method(self):
        app.config['TESTING'] = True
        app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
        self.client = app.test_client()
        with app.app_context():
            db.create_all()

    def teardown_method(self):
        with app.app_context():
            db.drop_all()

    def test_create_user(self):
        response = self.client.post('/api/users', json={
            'username': 'alice',
            'email': 'alice@example.com'
        })
        assert response.status_code == 201

Test Fixtures

Fixtures are reusable setup functions. pytest's fixture system lets you share common test data and setup across multiple tests.

@pytest.fixture
def sample_user():
    user = User(username='testuser', email='test@example.com')
    user.set_password('password123')
    db.session.add(user)
    db.session.commit()
    return user

def test_get_user(client, sample_user):
    response = client.get(f'/api/users/{sample_user.id}')
    assert response.status_code == 200
    assert response.json['username'] == 'testuser'

Mocking the Database

Sometimes you want to test logic without touching the real database. Mocking lets you fake database operations so tests run faster and focus on the logic you're testing.

from unittest.mock import patch, MagicMock

@patch('app.db.session')
def test_create_user(mock_session):
    user = User(username='alice', email='alice@example.com')
    mock_session.add = MagicMock()
    mock_session.commit = MagicMock()

    # Your function that creates a user
    create_user_in_db(user)

    mock_session.add.assert_called_once_with(user)
    mock_session.commit.assert_called_once()

Why Testing Catches Bugs Early

A bug found during development takes minutes to fix. The same bug found in production might take hours of debugging, patching, and redeploying — and your users deal with the broken feature in the meantime. Running tests before every commit means you catch problems when they're cheapest to fix.

Try it Yourself ->