Testing
Write unit, integration, and functional tests for splent_feature_notes using
SPLENT’s layered test structure.
Table of contents
- Prerequisites
- Test structure recap
- Step 1 – Set up fixtures
- Step 2 – Write unit tests
- Step 3 – Write integration tests
- Step 4 – Write functional tests
- Step 5 – Run the tests
- Step 6 – Check dependency consistency
- Step 7 – Batch test the product
- What you learned
- Next
Prerequisites
You completed Tutorial 3 and have splent_feature_notes working in my_first_app.
Test structure recap
Every feature has a layered test directory:
tests/
├── conftest.py # Shared fixtures
├── unit/ # Mocked dependencies, no DB, no HTTP
│ └── test_services.py
├── integration/ # Real test DB, repositories, services
│ └── test_repositories.py
├── functional/ # Flask test_client, full HTTP cycles
│ └── test_routes.py
└── e2e/ # Selenium (requires running server)
Step 1 – Set up fixtures
Edit splent_feature_notes/src/splent_io/splent_feature_notes/tests/conftest.py:
from splent_framework.fixtures.fixtures import * # noqa: F401,F403
import pytest
from splent_io.splent_feature_auth.models import User
from splent_io.splent_feature_notes.models import Notes
from splent_framework.db import db
@pytest.fixture(scope="function")
def auth_test_user(test_client, test_app):
with test_app.app_context():
user = User(email="notes_user@example.com", active=True)
user.set_password("1234")
db.session.add(user)
db.session.commit()
db.session.refresh(user)
return user
@pytest.fixture(scope="function")
def logged_in_client(test_client, auth_test_user):
test_client.post(
"/login",
data={"email": "notes_user@example.com", "password": "1234"},
follow_redirects=True,
)
return test_client
@pytest.fixture(scope="function")
def sample_note(test_client, test_app, auth_test_user):
with test_app.app_context():
note = Notes(
user_id=auth_test_user.id,
title="Test Note",
content="This is a test note.",
)
db.session.add(note)
db.session.commit()
db.session.refresh(note)
return note
The from splent_framework.fixtures.fixtures import * line imports shared fixtures provided by the framework: test_app (a configured Flask app with a test database) and test_client (a Flask test client). Every feature test file inherits these through conftest.py.
Step 2 – Write unit tests
Edit splent_feature_notes/src/splent_io/splent_feature_notes/tests/unit/test_services.py:
from unittest.mock import MagicMock
from splent_io.splent_feature_notes.services import NotesService
def test_create_note():
repo = MagicMock()
repo.create.return_value = MagicMock(id=1, title="My Note", user_id=1)
service = NotesService()
service.repository = repo
result = service.create(user_id=1, title="My Note", content="Hello")
repo.create.assert_called_once_with(user_id=1, title="My Note", content="Hello")
assert result.title == "My Note"
def test_get_by_user():
mock_note_1 = MagicMock(id=1, title="Note A", user_id=5)
mock_note_2 = MagicMock(id=2, title="Note B", user_id=5)
repo = MagicMock()
repo.get_by_column.return_value = [mock_note_1, mock_note_2]
service = NotesService()
service.repository = repo
results = service.get_by_user(5)
repo.get_by_column.assert_called_once_with("user_id", 5)
assert len(results) == 2
def test_delete_note():
repo = MagicMock()
repo.delete.return_value = True
service = NotesService()
service.repository = repo
result = service.delete(1)
repo.delete.assert_called_once_with(1)
assert result is True
Unit tests mock the repository entirely. No database, no Flask app, no HTTP. They run in milliseconds.
Step 3 – Write integration tests
Edit splent_feature_notes/src/splent_io/splent_feature_notes/tests/integration/test_repositories.py:
from splent_io.splent_feature_notes.models import Notes
from splent_io.splent_feature_notes.repositories import NotesRepository
def test_create_note(test_app, auth_test_user):
with test_app.app_context():
repo = NotesRepository()
note = repo.create(
user_id=auth_test_user.id,
title="Integration Note",
content="Created in test DB",
)
assert note.id is not None
assert note.title == "Integration Note"
assert note.user_id == auth_test_user.id
def test_get_by_column(test_app, auth_test_user):
with test_app.app_context():
repo = NotesRepository()
repo.create(user_id=auth_test_user.id, title="Note 1", content="First")
repo.create(user_id=auth_test_user.id, title="Note 2", content="Second")
notes = repo.get_by_column("user_id", auth_test_user.id)
assert len(notes) == 2
assert all(n.user_id == auth_test_user.id for n in notes)
def test_delete_note(test_app, auth_test_user):
with test_app.app_context():
repo = NotesRepository()
note = repo.create(
user_id=auth_test_user.id,
title="To Delete",
content="Will be removed",
)
note_id = note.id
result = repo.delete(note_id)
assert result is True
assert repo.get_by_id(note_id) is None
Integration tests use the real test database. The test_app fixture creates all tables, and each test function runs in its own transaction that is rolled back automatically.
Step 4 – Write functional tests
Edit splent_feature_notes/src/splent_io/splent_feature_notes/tests/functional/test_routes.py:
from flask import url_for
def test_notes_index_requires_login(test_client):
response = test_client.get("/notes/", follow_redirects=True)
assert response.status_code == 200
assert b"Login" in response.data
def test_notes_index_authenticated(logged_in_client):
response = logged_in_client.get("/notes/")
assert response.status_code == 200
assert b"My Notes" in response.data
def test_create_note(logged_in_client):
response = logged_in_client.post(
"/notes/create",
data={"title": "Functional Test Note", "content": "Created via test client"},
follow_redirects=True,
)
assert response.status_code == 200
assert b"Functional Test Note" in response.data
def test_create_note_missing_title(logged_in_client):
response = logged_in_client.post(
"/notes/create",
data={"title": "", "content": "No title provided"},
follow_redirects=True,
)
assert response.status_code == 200
def test_delete_note(logged_in_client, sample_note):
response = logged_in_client.post(
f"/notes/{sample_note.id}/delete",
follow_redirects=True,
)
assert response.status_code == 200
Functional tests use Flask’s test client to make real HTTP requests against the full application stack. Routes, templates, services, and the database are all exercised.
Step 5 – Run the tests
Run each level independently:
splent feature:test notes --unit
splent feature:test notes --integration
splent feature:test notes --functional
Or run all three levels at once (the default):
splent feature:test notes
Add -v for verbose output showing each test name and result:
splent feature:test notes -v
Use -k to filter by test name:
splent feature:test notes -k test_create_note
Step 6 – Check dependency consistency
splent product:validate
This verifies that the Python imports in splent_feature_notes are consistent with the UVL constraints. Since notes imports User from auth, and the UVL declares notes => auth, the check passes. If you imported from a feature without declaring the dependency in the UVL, this command would flag it.
Step 7 – Batch test the product
With the product selected, run tests across all features at once:
splent product:test --unit
This runs unit tests across all features in the product (auth, public, redis, session_redis, profile, and notes) in a single pass.
What you learned
| Concept | Summary |
|---|---|
| Unit tests | Mock the repository, test service logic in isolation, no DB needed |
| Integration tests | Use the real test database, verify repository queries work |
| Functional tests | Use Flask’s test client, verify routes return correct responses |
| Fixtures | test_app and test_client from the framework, custom fixtures in conftest.py |
| feature:test | Run tests by feature and level (--unit, --integration, --functional) |
| product:validate | Verify Python imports match UVL dependency declarations |
| product:test | Batch-run tests across all features in a product |
Next
Tutorial 5: Release and deploy – publish your feature to PyPI and deploy a production build.