Как указать, какое поле модели вызвало ошибку ValidationError? - PullRequest
1 голос
/ 27 мая 2019

Допустим, у меня есть простая модель Django:

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)

И я хотел бы провести модульный тест, который точно проверяет, повышается ли ValidationError полем description , а не полем amount (или любым другим).

Итак, у меня есть этот тест, который примитивно проверяет, присутствует ли поле description в 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())

Но мне не очень нравится использовать eval(), и я считаю, что есть более элегантный способ указать источник ValidationError. Как я могу это сделать?

РЕДАКТИРОВАТЬ: мой класс модели также включает переопределенные методы clean() и save(), поэтому валидаторы работают нормально.

Ответы [ 2 ]

2 голосов
/ 27 мая 2019

Я бы сделал что-то вроде этого, ValidationError имеет атрибут error_dict, который мы уже можем использовать для проверки этого

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')
0 голосов
/ 27 мая 2019

Не все ValidationError объекты имеют и error_dict.Мы можем получить это из реализации конструктора ValidationError [GitHub] .Это зависит от того, является ли message (первый параметр конструкции) словарем.

Однако, что мы можем сделать, это использовать getattr(..) [Python-doc] для этого с запасным значением, например:

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', <b>getattr(</b>e.exception, <b>'err_dict', {})</b>)

Так что, учитывая, что error_dict не существует, мы позволим getattr(..) вернуть пустой словарь, иследовательно, assertIn терпит неудачу.

Мы также можем реализовать служебную функцию для этого, например:

_singleton = object()

class SomeTestCase(TestCase):

    def <b>assertKeyInErrorDict</b>(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.<b>assertKeyInErrorDict</b>('description', e.exception)

Таким образом, вы можете добавить такое assertKeyInErrorDict в служебный класс, который обеспечивает дополнительное утверждениефункции, а затем использовать его во всех подклассах, удаляя много кода шаблон код.

...