简体   繁体   中英

Form valid in unit test even though ValidationError was raised

I'm testing my view for handling invalid form data. In my test case I'm submitting form with missing field and expect view to handle it by displaying error message. Here is a relevant snippet from clean in my form:

Form:

def clean(self):
    first_name = self.cleaned_data.get('first_name', '')
    last_name = self.cleaned_data.get('last_name', '')

    if not first_name or not last_name:
        print('Form is invalid')
        # This is always executed.
        raise ValidationError('All fields are required.')

    # other stuff

ValidationError is always raised, however, form.is_valid() returns True nonetheless:

View:

form = forms.EditProfileForm(request.POST, instance=user)

if form.is_valid():
    print('Form is valid')
    # Update user data. 
    # Always executed even though error was raised.

This leads to test failing, since I'm asserting that form validation will fail:

Test Case:

def test_invalid_data(self):
    formdata = {
        'first_name': 'Bruno',
        'last_name': '', # Missing data.
        'email': 'bruno@schulz.pl',
        'password': 'metamorphosis'
    }

    request = self.factory.post('/accounts/profile', formdata)
    request.user = self.user
    setup_messages(request)
    response = ProfileView.as_view()(request)

    self.assertEqual(response.status_code, 200)
    self.assertNotEqual(self.user.first_name, formdata['first_name']) # Fails.
    self.assertNotEqual(self.user.last_name, formdata['last_name'])
    self.assertNotEqual(self.user.email, formdata['email'])
    self.assertNotEqual(self.user.username, formdata['email'])

Form validation works properly on a live server (tested "by hand").

It looks like ValidationError is ignored during TestCase evaluation.

*Test output:

.Form is invalid
F..Form is valid
.
======================================================================
FAIL: test_invalid_data (.tests.ProfileTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/apps/accounts/tests.py", line 114, in test_invalid_data
    self.assertNotEqual(self.user.first_name, formdata['first_name'])
AssertionError: 'Bruno' == 'Bruno'

You haven't shown your full view, but usually you do return redirect('/success-url/') after a successful post. If your validation error was ignored, then the view would redirect with status code 302, and your test would fail on the earlier line self.assertEqual(response.status_code, 200)

You can access the form from the view with form = response.context['form'] . If you check form.is_valid() or form.errors in your test, you will see that your ValidationError has not been ignored.

Your issue is that your assertNotEqual checks are not testing what you think they are. When you validate a model form, the instance is modified. If you want to check whether the user has been modified in the database, you need to refresh it from the database first .

self.user.refresh_from_db()

Try using self.add_error('last_name', your_validation_error) instead of just raising the error:

def clean(self):
    first_name = self.cleaned_data.get('first_name', '')
    last_name = self.cleaned_data.get('last_name', '')

    if not first_name:
        print('Form is invalid: missing first name')
        # define but don't raise the error
        missing_first_name = ValidationError('First name field is required.')
        self.add_error('first_name', missing_first_name)
    if not last_name:
        print('Form is invalid: missing last name')
        # define but don't raise error
        missing_last_name = ValidationError('Last name field is required.')
        self.add_error('last_name', missing_last_name)
        # you could also add a string instead of an Error, but a ValidationError is recommended:
        # self.add_error('last_name', 'the last name field is missing')

# other stuff

Now form.is_valid() will return False.

This SO answer led me to the solution, and here are the add_error docs . You should be adding the error to the form with self.add_error , not raising the validation error.

(I did find this example in the docs where the error is raised instead of passed to self.add_error , which may be the source of your confusion, but that was for cleaning a specific field. It was not the form.clean() method. I am assuming you could catch that error and then add it with self.add_error ... but that is a different problem to solve. The point is, I didn't see examples in the docs where form.clean() should raise the ValidationError. It should add it to the form instead.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM