Test levels
How to write tests at each level (unit, integration, functional, and e2e), with real examples from the codebase.
Table of contents
Unit tests
Mock all external dependencies. No database, no Flask app, no HTTP. Fastest tests.
# splent_feature_auth/tests/unit/test_services.py
from unittest.mock import MagicMock, patch
@patch("splent_io.splent_feature_auth.services.login_user")
def test_authenticate_valid_user(mock_login_user):
mock_user = MagicMock()
mock_user.check_password.return_value = True
repo = MagicMock()
repo.get_by_email.return_value = mock_user
service = AuthenticationService(user_repository=repo)
result = service.login("user@example.com", "1234")
assert result is True
mock_login_user.assert_called_once_with(mock_user, remember=True)
Key points. No fixtures needed, mock repositories, patch external calls, test logic in isolation.
Integration tests
Real test database. Verify repositories, queries, and model relationships.
# splent_feature_profile/tests/integration/test_repositories.py
def test_create_and_retrieve_profile(test_client):
with test_client.application.app_context():
user = User(email="integration@test.com", active=True)
user.set_password("pass123")
db.session.add(user)
db.session.commit()
profile = UserProfile(user_id=user.id, name="Test", surname="User")
db.session.add(profile)
db.session.commit()
service = UserProfileService()
found = service.get_by_user_id(user.id)
assert found is not None
assert found.name == "Test"
Key points. Use test_client (clean DB per test), wrap in app context, test real queries.
Functional tests
HTTP endpoints through Flask’s test client. Full stack active.
# splent_feature_auth/tests/functional/test_login_flow.py
def test_login_success(test_client, auth_test_user):
response = test_client.post(
"/login",
data={"email": "user1@example.com", "password": "1234"},
follow_redirects=True,
)
assert response.status_code == 200
assert b"Invalid credentials" not in response.data
def test_notes_requires_login(test_client):
response = test_client.get("/notes")
assert response.status_code == 302
assert "login" in response.headers.get("Location", "").lower()
Key points. Use test_client or logged_in_client, assert on status codes, response data, redirects.
E2E tests
Real browser via Selenium. Slow, requires running server.
# splent_feature_notes/tests/e2e/test_browser.py
from splent_framework.environment.host import get_host_for_selenium_testing
from splent_framework.selenium.common import initialize_driver, close_driver
@pytest.fixture()
def browser():
driver = initialize_driver()
yield driver
close_driver(driver)
def test_index_page_loads(browser):
host = get_host_for_selenium_testing()
browser.get(f"{host}/notes")
assert "notes" in browser.page_source.lower()
Run with splent feature:test --e2e.
initialize_driver() launches a local Chrome by default (Firefox via SELENIUM_BROWSER or the browser argument). When SELENIUM_GRID_URL is set it attaches to a Selenium Grid instead, and get_host_for_selenium_testing() returns SELENIUM_TARGET_URL when set. This is required with a grid, because the browser container resolves the URL and its localhost is not the application. See E2E (Selenium) for details.
Test helpers
Auth helpers
from splent_framework.helpers.test_helpers_auth import login, logout
def test_protected_route(test_client):
login(test_client, "user@example.com", "secret")
response = test_client.get("/profile/edit")
assert response.status_code == 200
logout(test_client)
Extending test_client per file
Override test_client locally to seed file-specific data.
@pytest.fixture(scope="function")
def test_client(test_client):
with test_client.application.app_context():
admin = User(email="admin@example.com", active=True)
admin.set_password("admin1234")
db.session.add(admin)
db.session.commit()
yield test_client
See also
- Fixtures. Fixture patterns and scope rules
- Running tests. CLI commands