Hey y’all
I’ve been writing my first tests in django (and I like it).
However I was surprised to find out that assertRaises
stops the execution of a test.
Writing something like
def test_no_create_if_valid_False(self):
self.assertRaises(Exception, Foo.objects.create, valid=False)
self.assertEqual(0, Foo.objects.count())
Results in
django.db.transaction.TransactionManagementError
: An error occurred in the current transaction. You can’t execute queries until the end of the ‘atomic’ block.
I found out I could fix this by wrapping the assertRaises
try:
with transaction.atomic():
self.assertRaises(Exception, Foo.objects.create, valid=False)
except Exception:
pass
That’s quite verbose.
But what made me write this post is that if I want to check for another Exception in the same test, it silently does not execute the second call
# Raises Exception - transaction stopped
self.assertRaises(Exception, Foo.objects.create, valid=False)
# The following Exception is not raised but the test passes
self.assertRaises(Exception, Foo.objects.create, valid=True)
again wrapping the first assertRaises
would fix this.
So is this a bug? It made me learn about atomic wrappers in django (which is nice), but only after I got the TransactionManagementError
. I would have gone on writing multiple Exceptions inn one test wrongly assuming that they all fail.
It’s not a bug, your assertion catches any subclass of Exception
so also TransactionManagementError
:
TransactionManagementError
→ProgrammingError
→DatabaseError
→ Error
→Exception
.
@felixxm thanks again for the quick reply.
It’s not a bug, your assertion catches any subclass of Exception
so also TransactionManagementError
:
The assertion does not catch the exception.
def test_no_create_if_valid_False(self):
self.assertRaises(Exception, Foo.objects.create, valid=False)
self.assertEqual(0, Foo.objects.count())
raises the Exception. (namely the second evaluation to Foo)
I would expect the implementation in django.test.TestCase.asserEqual
to wrap the ‘plain’ assertRaises
with the “try - with atomic - catch” wrapper (or something more elaborate) I wrote above.
side note: I’m aware that broadly catching Exception
is not a good style, I just tried to come up with a MWE
I was talking about your second example with two subsequent self.assertRaises(Exception, ...)
calls.
assertRaises()
is from Python’s unittest
. IMO, Django shouldn’t add any implicit wrapping to it.
Thanks for the clarification.
Do I understand correctly, that you would consider the following best practice?
def test_valid_eq_False_fails_integrity__creating_nothing(self):
try:
with transaction.atomic():
self.assertRaises(IntegrityError, Foo.objects.create, valid=False)
except IntegrityError:
pass
self.assertEqual(0, Foo.objects.count())
Or am I missing something?
IntegrityError
is caught by assertRaises()
so try ... except
is unnecessary:
with transaction.atomic():
self.assertRaises(IntegrityError, Foo.objects.create, valid=False)
self.assertEqual(Foo.objects.count(), 0)
Alright that looks good.
When I was playing around the tests only passed after I added the try-except but that must have been something else.
Thanks a lot for taking the time to fill me in.
an alternative solution is to use TransactionTestCase
instead of TestCase
in your tests.
see this SO answer for more details