1

I have a Django model with a "title" CharField(unique=True). I have a unit test that asserts that creating a second instance with the same title throws an IntegrityError. (I'm using pytest and pytest-django.)

I have something like:

class Foo(models.Model):
    title = models.CharField(unique=True)

def test_title_is_unique(db):
    Foo.objects.create(title='foo')
    with pytest.raises(IntegrityError):
        Foo.objects.create(title='foo')

This works fine, except the above code doesn't include cleanup code. pytest-django doesn't clean up the database for you, so you need to register cleanup handlers when you create or save a model instance. Something like this:

def test_title_is_unique(request, db):
    foo = Foo.objects.create(title='foo')
    request.addfinalizer(foo.delete)
    with pytest.raises(IntegrityError):
        Foo.objects.create(title='foo')

Okay, that's fine. But what if the second .create() call erroneously succeeds? I still want to clean up that instance, but only if it (erroneously) gets created.

Here is what I settled on:

def test_title_is_unique(request, db):
    foo = Foo.objects.create(title='foo')
    request.addfinalizer(foo.delete)
    try:
        with pytest.raises(IntegrityError):
            new_foo = Foo.objects.create(title='foo')
    finally:
        if 'new_foo' in locals():
            request.addfinalizer(new_foo.delete)

This doesn't feel particularly elegant or Pythonic, not to mention there are a bunch of lines of code that really shouldn't be running.

How do I guarantee that the second model instance is cleaned up if created, but with fewer hoops to jump through, and/or using fewer lines of code?

Drewness
  • 5,004
  • 4
  • 32
  • 50
Frank T
  • 8,268
  • 8
  • 50
  • 67

1 Answers1

2

You should not need to worry about the cleanup. pytest-django's db fixture disables transactions for the test, executing the entire test in one transaction and rolling back the transaction at the end. This ensures the database remains clean.

If the test requires transaction there's the transactional_db fixture which will enable transactions (slower) and flush the entire contents of the db after the test (very slow) again cleaning up for you.

So if the cleanup does not happen then you should probably file a bug at pytest-django. But I would be surprised if this is the case unless I missed something important.

flub
  • 5,953
  • 27
  • 24
  • I'm not seeing pytest clean up the database in between tests at all. Let me do some digging and see if my setup is weird, and if not, I'll follow up with pytest-django. – Frank T Mar 19 '14 at 16:37
  • 1
    You are correct. I didn't mention that my project has multiple databases. Apparently Django's TestCase (which pytest-django uses under the hood for the db fixture) has a `multi_db` field that is False by default. Here is a relevant SO question: http://stackoverflow.com/questions/10121485/django-testcase-not-using-transactions-on-secondary-database – Frank T Mar 19 '14 at 18:46
  • So did you revert back to using Django's UnitTest classes then as the pytest-djagno docs suggests? If you use this Django feature it would be great if you could help design a good API in pytest-django for this so we can implement this properly in pytest-django. You can probably start a discussion either on an issue for pytest-django or on the py.test main mailing list. – flub Mar 20 '14 at 10:34
  • I opened an issue over on Github: https://github.com/pelme/pytest_django/issues/76 I'm open to ideas about an API for incorporating it into pytest-django. – Frank T Mar 20 '14 at 13:59