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.

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 activemodel lifecycle 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.