# Flask Testing Suite Comprehensive testing setup for Flask applications with pytest. ## Usage ```bash # Run all tests pytest # Run with coverage pytest --cov=app --cov-report=html # Run specific test file pytest tests/test_models.py # Run with verbose output pytest -v ``` ## Test Configuration ```python # pytest.ini [tool:pytest] testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* addopts = --cov=app --cov-report=term-missing --cov-report=html:htmlcov --strict-markers --disable-warnings markers = unit: Unit tests integration: Integration tests slow: Slow running tests auth: Authentication tests ``` ## Test Fixtures ```python # tests/conftest.py import pytest import tempfile import os from app import create_app from app.extensions import db from app.models import User, Post, Category from flask_login import login_user @pytest.fixture(scope='session') def app(): """Create test application.""" # Create temporary database db_fd, db_path = tempfile.mkstemp() app = create_app({ 'TESTING': True, 'SQLALCHEMY_DATABASE_URI': f'sqlite:///{db_path}', 'WTF_CSRF_ENABLED': False, 'SECRET_KEY': 'test-secret-key' }) with app.app_context(): db.create_all() yield app # Cleanup os.close(db_fd) os.unlink(db_path) @pytest.fixture def client(app): """Create test client.""" return app.test_client() @pytest.fixture def runner(app): """Create test CLI runner.""" return app.test_cli_runner() @pytest.fixture def db_session(app): """Create database session for testing.""" with app.app_context(): connection = db.engine.connect() transaction = connection.begin() # Configure session to use the connection db.session.configure(bind=connection) yield db.session # Rollback transaction transaction.rollback() connection.close() db.session.remove() @pytest.fixture def user(db_session): """Create test user.""" user = User( username='testuser', email='test@example.com', first_name='Test', last_name='User' ) user.set_password('testpass123') user.save() return user @pytest.fixture def admin_user(db_session): """Create admin user.""" admin = User( username='admin', email='admin@example.com', first_name='Admin', last_name='User', is_admin=True ) admin.set_password('adminpass123') admin.save() return admin @pytest.fixture def category(db_session): """Create test category.""" category = Category( name='Test Category', description='A test category' ) category.save() return category @pytest.fixture def post(db_session, user, category): """Create test post.""" post = Post( title='Test Post', content='This is a test post content.', slug='test-post', status='published', user_id=user.id, category_id=category.id ) post.save() return post @pytest.fixture def auth_headers(user): """Create authentication headers.""" # For API testing token = user.generate_auth_token() return {'Authorization': f'Bearer {token}'} ``` ## Model Testing ```python # tests/test_models.py import pytest from datetime import datetime from app.models import User, Post, Category from werkzeug.security import check_password_hash class TestUser: """Test User model.""" def test_user_creation(self, db_session): """Test user creation.""" user = User( username='newuser', email='new@example.com', first_name='New', last_name='User' ) user.set_password('password123') user.save() assert user.id is not None assert user.username == 'newuser' assert user.email == 'new@example.com' assert user.full_name == 'New User' assert user.is_active is True assert user.is_admin is False assert user.created_at is not None def test_password_hashing(self, user): """Test password hashing.""" user.set_password('newpassword') assert user.password_hash != 'newpassword' assert check_password_hash(user.password_hash, 'newpassword') assert user.check_password('newpassword') assert not user.check_password('wrongpassword') def test_user_repr(self, user): """Test user string representation.""" assert repr(user) == '' def test_user_to_dict(self, user): """Test user dictionary conversion.""" user_dict = user.to_dict() assert 'username' in user_dict assert 'email' in user_dict assert 'password_hash' not in user_dict # Should be excluded def test_user_relationships(self, user, post): """Test user relationships.""" assert post in user.posts assert user.posts.count() == 1 class TestPost: """Test Post model.""" def test_post_creation(self, db_session, user, category): """Test post creation.""" post = Post( title='New Post', content='New post content', slug='new-post', status='draft', user_id=user.id, category_id=category.id ) post.save() assert post.id is not None assert post.title == 'New Post' assert post.author == user assert post.category == category assert not post.is_published def test_published_status(self, post): """Test post published status.""" assert post.is_published # Published with published_at post.status = 'draft' assert not post.is_published ``` ## View Testing ```python # tests/test_views.py import pytest from flask import url_for from app.models import User class TestMainViews: """Test main application views.""" def test_home_page(self, client): """Test home page.""" response = client.get('/') assert response.status_code == 200 assert b'Welcome' in response.data def test_about_page(self, client): """Test about page.""" response = client.get('/about') assert response.status_code == 200 class TestUserViews: """Test user-related views.""" def test_user_list(self, client, user): """Test user list page.""" response = client.get('/users/') assert response.status_code == 200 assert user.username.encode() in response.data def test_user_detail(self, client, user): """Test user detail page.""" response = client.get(f'/users/{user.id}') assert response.status_code == 200 assert user.username.encode() in response.data def test_user_create_get(self, client): """Test user creation form.""" response = client.get('/users/create') assert response.status_code == 200 assert b'Create User' in response.data def test_user_create_post(self, client, db_session): """Test user creation submission.""" data = { 'username': 'newuser', 'email': 'new@example.com', 'first_name': 'New', 'last_name': 'User', 'password': 'password123', 'confirm_password': 'password123' } response = client.post('/users/create', data=data, follow_redirects=True) assert response.status_code == 200 # Check user was created user = User.query.filter_by(username='newuser').first() assert user is not None assert user.email == 'new@example.com' def test_user_edit(self, client, user): """Test user editing.""" data = { 'username': user.username, 'email': 'updated@example.com', 'first_name': 'Updated', 'last_name': 'User' } response = client.post(f'/users/{user.id}/edit', data=data, follow_redirects=True) assert response.status_code == 200 # Refresh user from database db_session.refresh(user) assert user.email == 'updated@example.com' assert user.first_name == 'Updated' ``` ## Authentication Testing ```python # tests/test_auth.py import pytest from flask import url_for from app.models import User class TestAuthentication: """Test authentication functionality.""" def test_login_page(self, client): """Test login page access.""" response = client.get('/auth/login') assert response.status_code == 200 assert b'Login' in response.data def test_valid_login(self, client, user): """Test valid user login.""" data = { 'username': user.username, 'password': 'testpass123' } response = client.post('/auth/login', data=data, follow_redirects=True) assert response.status_code == 200 assert b'Welcome' in response.data def test_invalid_login(self, client, user): """Test invalid login credentials.""" data = { 'username': user.username, 'password': 'wrongpassword' } response = client.post('/auth/login', data=data) assert response.status_code == 200 assert b'Invalid' in response.data def test_logout(self, client, user): """Test user logout.""" # Login first with client.session_transaction() as sess: sess['_user_id'] = str(user.id) response = client.get('/auth/logout', follow_redirects=True) assert response.status_code == 200 def test_register_page(self, client): """Test registration page.""" response = client.get('/auth/register') assert response.status_code == 200 assert b'Register' in response.data def test_valid_registration(self, client, db_session): """Test valid user registration.""" data = { 'username': 'newuser', 'email': 'new@example.com', 'first_name': 'New', 'last_name': 'User', 'password': 'password123', 'confirm_password': 'password123' } response = client.post('/auth/register', data=data, follow_redirects=True) assert response.status_code == 200 # Check user was created user = User.query.filter_by(username='newuser').first() assert user is not None ``` ## API Testing ```python # tests/test_api.py import pytest import json from flask import url_for class TestUserAPI: """Test User API endpoints.""" def test_get_users(self, client, user): """Test GET /api/users.""" response = client.get('/api/users') assert response.status_code == 200 data = json.loads(response.data) assert 'users' in data assert len(data['users']) >= 1 def test_get_user(self, client, user): """Test GET /api/users/.""" response = client.get(f'/api/users/{user.id}') assert response.status_code == 200 data = json.loads(response.data) assert data['username'] == user.username assert data['email'] == user.email def test_create_user(self, client, db_session): """Test POST /api/users.""" user_data = { 'username': 'apiuser', 'email': 'api@example.com', 'first_name': 'API', 'last_name': 'User', 'password': 'password123' } response = client.post( '/api/users', data=json.dumps(user_data), content_type='application/json' ) assert response.status_code == 201 data = json.loads(response.data) assert data['username'] == 'apiuser' def test_update_user(self, client, user, auth_headers): """Test PUT /api/users/.""" update_data = { 'email': 'updated@example.com', 'first_name': 'Updated' } response = client.put( f'/api/users/{user.id}', data=json.dumps(update_data), content_type='application/json', headers=auth_headers ) assert response.status_code == 200 data = json.loads(response.data) assert data['email'] == 'updated@example.com' def test_delete_user(self, client, user, auth_headers): """Test DELETE /api/users/.""" response = client.delete( f'/api/users/{user.id}', headers=auth_headers ) assert response.status_code == 204 ``` ## Test Utilities ```python # tests/utils.py import json from flask import url_for def login_user(client, username, password): """Helper to login user in tests.""" return client.post('/auth/login', data={ 'username': username, 'password': password }, follow_redirects=True) def logout_user(client): """Helper to logout user in tests.""" return client.get('/auth/logout', follow_redirects=True) def assert_json_response(response, expected_status=200): """Assert JSON response format and status.""" assert response.status_code == expected_status assert response.content_type == 'application/json' return json.loads(response.data) def create_test_data(db_session): """Create common test data.""" from app.models import User, Category, Post # Create test users users = [] for i in range(3): user = User( username=f'user{i}', email=f'user{i}@example.com', first_name=f'User{i}', last_name='Test' ) user.set_password('password123') user.save() users.append(user) return {'users': users} ``` ## Performance Testing ```python # tests/test_performance.py import pytest import time from app.models import User @pytest.mark.slow class TestPerformance: """Test application performance.""" def test_user_query_performance(self, db_session): """Test user query performance.""" # Create multiple users users = [] for i in range(100): user = User( username=f'perfuser{i}', email=f'perf{i}@example.com', first_name=f'Perf{i}', last_name='User' ) users.append(user) db_session.bulk_save_objects(users) db_session.commit() # Test query performance start_time = time.time() result = User.query.all() end_time = time.time() assert len(result) >= 100 assert (end_time - start_time) < 0.1 # Should complete in under 100ms def test_endpoint_response_time(self, client): """Test endpoint response time.""" start_time = time.time() response = client.get('/') end_time = time.time() assert response.status_code == 200 assert (end_time - start_time) < 0.5 # Should respond in under 500ms ```