Reputation: 5753
Let's say I have a simple Django model:
class Transaction(models.Model):
description = models.CharField('description', max_length=150,
validators=[MinLengthValidator(2, 'Description\'s min length is 2'), ])
amount = models.DecimalField('amount', max_digits=10, decimal_places=2,
validators=[MinValueValidator(1, 'Min value is 1'), ])
user = models.ForeignKey(User)
# to trigger model fields' validation
def clean(self, *args, **kwargs):
super(Transaction, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(Transaction, self).save(*args, **kwargs)
And I'd like to have a unit test, which precisely checks whether ValidationError
is raised by description field and not by amount field (or any other).
So I have this piece of test, which in a primitive way checks if description field is present in e.exception
:
def test_model_requires_description_min_2_characters(self):
with self.assertRaises(ValidationError) as e:
Transaction.objects.create(description='a', amount="50", user=self.user1)
err_dict = eval(str(e.exception))
self.assertIn('description', err_dict.keys())
But I don't really like to use eval()
and I believe there is more elegant way to indicate the source of ValidationError
. How can I do this?
EDIT: my model class also includes overriden clean()
and save()
methods, so validators are running fine.
Upvotes: 1
Views: 127
Reputation: 477210
Not all ValidationError
objects have an error_dict
. We can derive that from the implementation of the constructor of the ValidationError
[GitHub]. It depends on whether the message
(the first parameter of the construct) is a dictionary.
What we can do however is use getattr(..)
[Python-doc] for this, with a fallback value, like:
def test_model_requires_description_min_2_characters(self):
with self.assertRaises(ValidationError) as e:
Transaction.objects.create(description='a', amount="50", user=self.user1)
self.assertIn('description', getattr(e.exception, 'err_dict', {}))
So given the error_dict
does not exists, we will let getattr(..)
return an empty dictionary, and hence the assertIn
fails.
We can also implement a utility function for this, like:
_singleton = object()
class SomeTestCase(TestCase):
def assertKeyInErrorDict(self, key, error):
error_dict = getattr(error, 'err_dict', _singleton)
if error_dict is _singleton:
self.fail('The error {} has no error_dict'.format(error))
else:
self.assertIn(key, error_dict)
def test_model_requires_description_min_2_characters(self):
with self.assertRaises(ValidationError) as e:
Transaction.objects.create(description='a', amount="50", user=self.user1)
self.assertKeyInErrorDict('description', e.exception)
You can thus ass such assertKeyInErrorDict
in a utility class that provides extra assert functions, and then use it in all subclasses, removing a lot of boilerplate code.
Upvotes: 0
Reputation: 32284
I would do something like this, ValidationError
has an attribute error_dict
that we can already use to test for this
def test_model_requires_description_min_2_characters(self):
try:
Transaction.objects.create(description='a', amount="50", user=self.user1)
except ValidationError as e:
# A ValidationError was raised, now we test to see if our field is in it
self.assertIn('description', e.error_dict.keys())
else:
# No exception was raised, raise our own exception
raise Exception('The test failed')
Upvotes: 2