Skip to content

Troubleshooting


Setup errors

AttributeError: 'Config' object has no attribute '_mrt_config'

AttributeError: 'Config' object has no attribute '_mrt_config'

Cause: conftest.py is missing or the pytest_configure function isn't set up.

Fix: Create conftest.py in your project root (where you run pytest from):

# conftest.py
from pytest_mrt import MRTConfig

def pytest_configure(config):
    config._mrt_config = MRTConfig(
        alembic_ini="alembic.ini",
        db_url="postgresql://localhost/myapp_test",
    )

Make sure pytest_configure is a top-level function, not inside a class.


FileNotFoundError: alembic.ini not found

Cause: The path to alembic.ini is wrong relative to where you run pytest.

Fix: Run pytest from the directory that contains alembic.ini, or use an absolute path:

import os

MRTConfig(
    alembic_ini=os.path.join(os.path.dirname(__file__), "alembic.ini"),
    db_url="...",
)

could not connect to server: Connection refused

sqlalchemy.exc.OperationalError: (psycopg2.OperationalError)
could not connect to server: Connection refused

Cause: The database server isn't running, or the URL is wrong.

Fix:

# Check PostgreSQL is running
pg_isready -h localhost

# Test the connection manually
psql postgresql://localhost/myapp_test

# Check the URL format
# postgresql://user:password@host:port/dbname
# sqlite:///path/to/file.db     (relative)
# sqlite:////absolute/path.db   (absolute, 4 slashes)

Target database is not up to date

alembic.util.exc.CommandError: Target database is not up to date.

Cause: pytest-mrt starts from alembic downgrade base, but Alembic's base state requires the migration history to be clean.

Fix: Make sure your test database has no leftover state from previous test runs. Add to your conftest.py:

from pytest_mrt import MRTConfig
from sqlalchemy import create_engine, text

def pytest_configure(config):
    db_url = "postgresql://localhost/myapp_test"

    # Drop and recreate the test database before each run
    engine = create_engine(db_url)
    with engine.connect() as conn:
        conn.execute(text("DROP SCHEMA public CASCADE"))
        conn.execute(text("CREATE SCHEMA public"))
    engine.dispose()

    config._mrt_config = MRTConfig(alembic_ini="alembic.ini", db_url=db_url)

Test failures

Table 'X' no longer exists after rollback

FAILED: Table 'users' no longer exists after rollback — all data lost

Cause: The migration drops a table in upgrade() and doesn't recreate it in downgrade(), OR downgrade() is a no-op that doesn't reverse a CREATE TABLE.

Which migration is it? Run mrt check to find it immediately:

mrt check migrations/versions/

Common fix for CREATE TABLE with no-op downgrade:

def downgrade():
    op.drop_table("users")  # reverse the CREATE TABLE


X/Y rows lost after rollback

FAILED: Table 'users': 3/3 rows lost after rollback

Cause: The migration drops a column or truncates data in upgrade(). The schema comes back in downgrade(), but the actual row data was destroyed.

This is the most important thing pytest-mrt catches. The migration passes alembic downgrade but leaves your data in a bad state.

Which column? Look for op.drop_column() in the migration's upgrade() function. The static analysis will have caught it too:

mrt check migrations/versions/
# Should show: DROP COLUMN in upgrade → error

Column 'X' value changed after rollback

FAILED: Table 'users': column 'status' value changed after rollback
(expected 'active', got 'inactive')

Cause: The downgrade() function modifies data incorrectly — it doesn't fully restore the pre-migration state.

Fix: Check your downgrade() UPDATE statements. The values after rollback must exactly match what existed before the migration ran.


Table 'X' still exists after rollback — downgrade is incomplete

FAILED: Table 'events' still exists after rollback — downgrade is incomplete

Cause: The migration creates a table in upgrade() but downgrade() is pass or empty.

Fix:

def downgrade():
    op.drop_table("events")


Column 'X' still present after rollback — downgrade is incomplete

Cause: The migration adds a column in upgrade() but doesn't remove it in downgrade().

Fix:

def downgrade():
    op.drop_column("users", "bio")


Static analysis

A pattern is flagged but my migration is intentional

Some patterns are warnings, not errors. If your migration intentionally drops a column (for example, after a confirmed data migration), you can:

  1. Acknowledge it — the warning tells reviewers to double-check the PR
  2. Run with --strict only on errors — omit --strict to let warnings pass CI

There is no per-migration skip/ignore mechanism yet — it's on the roadmap.


mrt check reports Multiple heads

error: Revisions 002a, 002b all branch from '001' — migration graph has multiple heads

Cause: Two developers created migrations independently from the same parent revision.

Fix:

alembic merge heads -m "merge 002a and 002b"
alembic upgrade head

This creates a merge migration that resolves the conflict.


False positive on op.execute()

op.execute() is flagged as a warning when the downgrade() function has no corresponding op.execute(). If both upgrade() and downgrade() have op.execute(), no warning is raised.


Performance

Tests are slow

check_all() runs downgrade base → upgrade revision N for each revision to ensure clean state. For large migration histories (100+ revisions), this can be slow.

Options:

  1. Test only recent migrations in CI:

    def test_recent_migration(mrt):
        # Only test the migration added in this PR
        result = mrt.check_revision("abc1234")
        assert result.passed, result.failure_summary()
    

  2. Run check_all() on a schedule (nightly) rather than on every PR.

  3. Use SQLite for local development — SQLite is much faster than PostgreSQL for migration testing.


Getting help

If you've hit something not covered here, open an issue with:

  • The migration file that's causing the problem
  • The full error output
  • Your database type and version
  • Your pytest-mrt version (mrt version)