Testing

Write unit, integration, and functional tests for splent_feature_notes using SPLENT’s layered test structure.


Table of contents

  1. Prerequisites
  2. Test structure recap
  3. Step 1 – Set up fixtures
  4. Step 2 – Write unit tests
  5. Step 3 – Write integration tests
  6. Step 4 – Write functional tests
  7. Step 5 – Run the tests
  8. Step 6 – Check dependency consistency
  9. Step 7 – Batch test the product
  10. What you learned
  11. 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.


Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es