Testing and State Management
Because the Pagemark API uses an in-memory storage system, data persists for the lifetime of the application process. When running automated tests, this shared state can lead to "leaky" tests where data from one test case affects the outcome of another.
This tutorial walks you through using internal repository and service helpers to verify entity counts and reset the application state between test runs.
Prerequisites
To follow this tutorial, you should be familiar with the following classes:
app.db.repository.BookmarkRepository: The low-level in-memory data store.app.services.bookmark_service.BookmarkService: The singleton facade that manages the repository, cache, and search index.
Step 1: Verifying State with _count_all
Before performing assertions in your tests, you often need to verify the current number of entities in the system. The BookmarkRepository provides a diagnostic helper called _count_all() that returns the current counts for bookmarks, tags, and collections.
from app.db.repository import BookmarkRepository
from app.models.bookmark import Bookmark
# Initialize repository and add a bookmark
repo = BookmarkRepository()
new_bookmark = Bookmark(url="https://example.com", title="Example")
repo.save_bookmark(new_bookmark)
# Check entity counts
counts = repo._count_all()
print(f"Current state: {counts}")
# Output: Current state: {'bookmarks': 1, 'tags': 0, 'collections': 0}
The _count_all() method returns a dictionary containing the lengths of the internal _bookmarks, _tags, and _collections dictionaries. This is useful for verifying that an operation (like a delete) actually reduced the count as expected.
Step 2: Wiping the Repository with _clear_all
If you are working directly with the repository in a unit test, you can use _clear_all() to wipe all data without re-instantiating the class.
from app.db.repository import BookmarkRepository
from app.models.bookmark import Bookmark
repo = BookmarkRepository()
repo.save_bookmark(Bookmark(url="https://test.com", title="Test"))
# Wipe all data
repo._clear_all()
# Verify the repository is empty
assert repo._count_all()["bookmarks"] == 0
_clear_all() calls .clear() on all internal storage dictionaries. Note that this only affects the repository; if you are using the BookmarkService, the service's internal cache and search index will still hold references to the old data.
Step 3: Performing a Full Reset with BookmarkService._reset
In integration tests, you typically interact with the BookmarkService singleton. Because the service manages a cache (LRUCache) and a search index (SearchIndex) in addition to the repository, clearing the repository alone is insufficient.
The BookmarkService provides a _reset() method specifically for testing. It re-initializes all internal components, ensuring a completely clean slate.
from app.services.bookmark_service import BookmarkService
# Get the singleton instance
service = BookmarkService()
# Perform a full reset (wipes repo, cache, and search index)
service._reset()
# Verify the service is empty via the repository it manages
counts = service._repo._count_all()
assert counts["bookmarks"] == 0
When you call _reset(), the service executes _init_services(), which creates a fresh BookmarkRepository, a new LRUCache with a default max_size of 256, and a new SearchIndex.
Step 4: Implementing a Test Fixture
The most effective way to use these helpers is within a test fixture. This ensures that every test starts with a clean environment.
If you are using pytest, you can create a fixture in your conftest.py or within your test file:
import pytest
from app.services.bookmark_service import BookmarkService
@pytest.fixture(autouse=True)
def clean_state():
"""Reset the BookmarkService singleton before every test."""
service = BookmarkService()
service._reset()
yield service
def test_create_bookmark_increases_count(clean_state):
# Arrange
service = clean_state
# Act
service.create_bookmark({"url": "https://pagemark.io", "title": "Pagemark"})
# Assert
counts = service._repo._count_all()
assert counts["bookmarks"] == 1
def test_another_isolated_test(clean_state):
# This test starts with 0 bookmarks because of the autouse fixture
service = clean_state
counts = service._repo._count_all()
assert counts["bookmarks"] == 0
By using BookmarkService()._reset() in a setup phase, you prevent state leakage and ensure that your automated testing cycles are reliable and repeatable.
Next Steps
- Explore
app.services.search_service.SearchIndexto see how the search state is managed. - Review
app.services._cache.LRUCacheto understand how the service-level cache handles entity retrieval.