Pytest Integration¶
activemodel provides comprehensive testing utilities to manage database state and model factories during tests.
Configuration¶
You can configure which tables are preserved (not truncated) when using the truncation reset strategy.
activemodel_preserve_tables¶
This option specifies a list of tables to keep during database truncation. The alembic_version table is always preserved by default.
pytest.ini:
[pytest]
activemodel_preserve_tables = alembic_version,zip_codes,seed_data
pyproject.toml:
[tool.pytest.ini_options]
activemodel_preserve_tables = [
"alembic_version",
"zip_codes",
"seed_data",
]
Database Reset Strategies¶
Testing database-backed applications requires a reliable way to reset the database between tests. activemodel supports two main strategies.
Transaction Reset (Recommended)¶
The most efficient way to reset the database is to wrap each test in a transaction and roll it back. This is provided by the database_reset_transaction function.
To use this, create an autouse fixture in your conftest.py:
import pytest
from activemodel.pytest import database_reset_transaction
@pytest.fixture(scope="function", autouse=True)
def db_reset():
yield from database_reset_transaction()
This strategy ensures that any changes made during the test are never committed. Crucially, BaseModel.save() and ActiveModelFactory.save() will automatically use this transaction’s session, so you don’t need to manually manage sessions in your tests.
Truncation Reset¶
In scenarios where transactions aren’t suitable (e.g., integration tests where code runs in a separate process or manages its own transactions), use the truncation strategy.
import pytest
from activemodel.pytest import database_reset_truncate
@pytest.fixture(scope="function", autouse=True)
def db_reset(pytestconfig):
database_reset_truncate(pytest_config=pytestconfig)
Hybrid Strategy (Integration + Unit)¶
For complex projects, you may want to use transactions for speed in unit tests but truncation for reliability in integration tests. Here is a recommended pattern:
from pathlib import Path
import pytest
from activemodel.pytest import database_reset_transaction, database_reset_truncate
def is_integration_test(request):
return "integration" in str(request.node.path)
@pytest.fixture(scope="function", autouse=True)
def db_reset(request):
if is_integration_test(request):
database_reset_truncate(pytest_config=request.config)
yield
else:
yield from database_reset_transaction()
Fixtures¶
The activemodel plugin provides several fixtures out of the box.
db_session¶
Provides a database session for the duration of a test. While models and factories use the global session automatically, db_session is useful for manual queries or assertions.
def test_user_creation(db_session):
user = User(email="test@example.com").save()
# db_session is already configured to point to the test transaction
count = db_session.query(User).count()
assert count == 1
db_truncate_session¶
When using the truncation strategy, models created by factories are often detached from any active session. db_truncate_session fixes this by setting a global session that factories will use, keeping objects attached and navigable.
Best Practices¶
Session Initialization¶
Ensure that your database is initialized before the tests run, and that application modules (especially models) are not imported until the environment is set to test.
# conftest.py
import os
os.environ["PYTHON_ENV"] = "test"
import activemodel
import my_app.models
def pytest_sessionstart(session):
activemodel.init("postgresql://...")
# Initial cleanup of any cruft from previous failed runs
database_reset_truncate(pytest_config=session.config)
Factory Persistence¶
Always prefer Factory.save() over Factory.build() when testing database interactions. ActiveModelFactory ensures the object is persisted and then refreshed so that all attributes (like autogenerated IDs or timestamps) are available.
Factory Integration¶
activemodel includes built-in support for polyfactory to make generating test data easy.
ActiveModelFactory¶
The ActiveModelFactory automatically handles persistence and session management.
from activemodel.pytest.factories import ActiveModelFactory
from my_app.models import User
class UserFactory(ActiveModelFactory[User]):
__model__ = User
def test_something():
# Builds and saves the model to the database
user = UserFactory.save(email="custom@example.com")
Key features of ActiveModelFactory:
Automatic Saving:
Factory.save()persists the model using the current test session.Relationship Handling: Includes hooks to intelligently handle or ignore relationship fields and primary keys during generation.
Lifecycle Support: Works seamlessly with
activemodellifecycle hooks.
Reference Implementation¶
For a complete, real-world example of how to configure activemodel with Pytest for both unit and integration (browser-based) tests, see the python-starter-template.