# 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**: ```ini [pytest] activemodel_preserve_tables = alembic_version,zip_codes,seed_data ``` **pyproject.toml**: ```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`: ```python 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. ```python 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: ```python 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. ```python 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`. ```python # 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](https://github.com/litestar-org/polyfactory) to make generating test data easy. ### `ActiveModelFactory` The `ActiveModelFactory` automatically handles persistence and session management. ```python 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](https://github.com/iloveitaly/python-starter-template).