We write our test suite using nose function tests for a variety of reasons.
When running the test suite for our Django application, we would like to avoid leaking any data out of these tests (as with django.test.TestCase
), because that leads to coupling and hard to diagnose failures.
The most obvious means of solving this is a decorator which we can just wrap around tests that we want to be cleaned-up after, but I'm not married to that if a different solution can get us what we want.
We run on PostgreSQL, so Postgres-specific solutions would be fine.
I've spent a bit of time looking at this today, and have come up with the following decorator:
from functools import wraps
from django.db import transaction
from mock import patch
def rollback_db_changes(func):
"""Decorate a function so that it will be rolled back once completed."""
@wraps(func)
@transaction.commit_manually
def new_f(*args, **kwargs):
def fake_commit(using=None):
# Don't properly commit the transaction, so we can roll it back
transaction.set_clean(using)
patcher = patch('django.db.transaction.commit', fake_commit)
patcher.start()
try:
return func(*args, **kwargs)
finally:
patcher.stop()
transaction.rollback()
return new_f
We perform the patching so that the Django test client doesn't close the transaction without us being able to roll it back. This allows the following tests to pass:
from django.contrib.auth.models import User
@rollback_db_changes
def test_allowed_access():
user = User.objects.create(username='test_user')
eq_(1, User.objects.count())
@rollback_db_changes
def test_allowed_access_2():
user = User.objects.create(username='test_user')
eq_(1, User.objects.count())
Previously the second test to run could not create a user with a duplicated user name.