Using pytest fixtures and peewee transactions together
<p>I'm writing a set of unit tests using <code>pytest</code> for some database models that are implemented using <code>peewee</code>. I would like to use database transactions (the database is a Postgres one, if that is relevant) in order to roll back any database changes after each test. </p>

<p>I have a situation where I would like to use two fixtures in a test, but have both fixtures clean up their database models via the <code>rollback</code> method, like so:</p>

<pre><code>@pytest.fixture
def test_model_a():
    with db.transaction() as txn:  # `db` is my database object
        yield ModelA.create(...)
        txn.rollback()

@pytest.fixture
def test_model_b():
    with db.transaction() as txn:  # `db` is my database object
        yield ModelB.create(...)
        txn.rollback()

def test_models(test_model_a, test_model_b):
    # ...
</code></pre>

<p>This works, but reading the <a href="http://peewee.readthedocs.io/en/latest/peewee/transactions.html#explicit-transaction" rel="nofollow">documentation for <code>peewee</code></a> suggests that this is error prone:</p>

<blockquote>
  <p>If you attempt to nest transactions with peewee using the <code>transaction()</code> context manager, only the outer-most transaction will be used. However if an exception occurs in a nested block, this can lead to unpredictable behavior, so it is strongly recommended that you use <code>atomic()</code>.</p>
</blockquote>

<p>However, <code>atomic()</code> does not provide a <code>rollback()</code> method. It seems that when managing transactions explicitly, the key is to use an outer-most <code>transaction()</code>, and use <code>savepoint()</code> context managers within that transaction. But in my test code above, both fixtures are on the same "level", so to speak, and I don't know where to create the transaction, and where to create the savepoint.</p>

<p>My only other idea is to use the order that the fixtures get evaluated to decide where to put the transaction (<a href="http://stackoverflow.com/questions/25660064/in-which-order-are-pytest-fixtures-executed">which seems to be alphabetical</a>), but this seems very brittle indeed.</p>

<p>Is there a way to achieve this? Or does my test design need re-thinking?</p>

<p>If you want to rollback all transactions created within a test, you could have a fixture which takes care of the transaction itself and make the model fixtures use it:</p>

<pre><code>@pytest.fixture
def transaction():
    with db.transaction() as txn:  # `db` is my database object
        yield txn
        txn.rollback()

@pytest.fixture
def test_model_a(txn):
    yield ModelA.create(...)        

@pytest.fixture
def test_model_b(txn):
    yield ModelB.create(...)            

def test_models(test_model_a, test_model_b):
    # ...
</code></pre>

<p>This way all models are created within the same transaction and rolled back at the end of the test.        </p>

python postgresql transactions py.test peewee