for loop in django unittest

I want to test register view in django project,

so I build some fake test cases(self.correct_samples)

after register successfully, it should redirect to home page which means the status code should be 302.

from django.test import TestCase
from django.urls.base import reverse


class RegisterTests(TestCase):

    def setUp(self):
        url = reverse('account:register')
        self.response = self.client.get(url)

        self.correct_samples = (
            ('testuser1@email.com', 'testuser', 'test112233', 'test112233'),
            ('fakeuser@email.com', 'fake123', 'fakeuser111', 'fakeuser111'),
            ('correct@email.com', 'Jack', 'myfavorite', 'myfavorite'),

            ('failemail', 'Jack', 'myfavorite', 'myfavorite'),  # fail for purpose
        )

    def test_register_form(self):

        for test_case in self.correct_samples:

            email, username, password1, password2 = test_case
            self.response = self.client.post(reverse('account:register'), data={
                'email': email,
                'username': username,
                'password1': password1,
                'password2': password2,
            })

            self.assertEqual(self.response.status_code, 302)
            self.assertRedirects(
                self.response, expected_url='/', status_code=302, target_status_code=200)

The fourth data in self.correct_samples which is ('failemail', 'Jack', 'myfavorite', 'myfavorite') should be a fail case.

but after python manage.py test. It passed.

(env) C:\Users\User\myblog>python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
C:\Users\User\myblog\env\lib\site-packages\whitenoise\base.py:115: UserWarning: No directory at: C:\Users\User\myblog\staticfiles\
  warnings.warn(u"No directory at: {}".format(root))
.
----------------------------------------------------------------------
Ran 1 test in 0.195s

OK
Destroying test database for alias 'default'...

Here comes the tricky thing,

it failed after switching order from fourth to first.

from django.test import TestCase
from django.urls.base import reverse


class RegisterTests(TestCase):

    def setUp(self):
        ...

        self.correct_samples = (
            ('failemail', 'Jack', 'myfavorite', 'myfavorite'),  # fail for purpose

            ('testuser1@email.com', 'testuser', 'test112233', 'test112233'),
            ('fakeuser@email.com', 'fake123', 'fakeuser111', 'fakeuser111'),
            ('correct@email.com', 'Jack', 'myfavorite', 'myfavorite'),
        )

    def test_register_form(self):
        ...

result:

(env) C:\Users\User\myblog>python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
C:\Users\User\myblog\env\lib\site-packages\whitenoise\base.py:115: UserWarning: No directory at: C:\Users\User\myblog\staticfiles\
  warnings.warn(u"No directory at: {}".format(root))
F
======================================================================
FAIL: test_register_form (account.tests.RegisterTests)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\User\myblog\account\tests.py", line 34, in test_register_form
    self.assertEqual(self.response.status_code, 302)
AssertionError: 200 != 302

----------------------------------------------------------------------
Ran 1 test in 0.026s

FAILED (failures=1)
Destroying test database for alias 'default'...

Why this happened?

I have searched for related keywords like unittest in forloop, multiple testcases in forloop.

but it seems no answers for it, or maybe search through other keywords?

or something I missed or misunderstood?

thanks for helping.

Hi Yang

The problem you’re seeing is almost certainly a lack of isolation between your tests.
I can’t quite work it out without doing debugging with your actual view code.
But I can help you improve your tests.

Running several tests in one function is not a good idea, because they cannot run and fail independently.
If the a test fails, we get no information about the later tests, since they don’t run.
Plus it increases the chance that something will “leak” between the tests, causing the kind of bug you’re seeing.

unittest does provide the subTest() helper, but I prefer to write independent tests, or use parameterized.
Independent tests are great since we can name them to provide some information on what we’re looking for.
So to do that with your tests:

from django.test import TestCase
from django.urls.base import reverse


class RegisterTests(TestCase):
        
    def check_register(self, email, username, password1, password2):
        response = self.client.post(reverse('account:register'), data={
            'email': email,
            'username': username,
            'password1': password1,
            'password2': password2,
        })

        self.assertEqual(response.status_code, 302)
        self.assertRedirects(
            response, expected_url='/', status_code=302, target_status_code=200)

    def test_register_long_password(self):
        self.check_register('testuser1@example.com', 'testuser', 'test112233', 'test112233')
    
    def test_register_numerical_username(self):
        self.check_register('fakeuser@example.com', 'fake123', 'fakeuser111', 'fakeuser111')
    
    def test_register_capitalized_username(self):
        self.check_register('correct@example.com', 'Jack', 'myfavorite', 'myfavorite')
    
    def test_register_fail_bad_email(self):
        self.check_register('failemail', 'Jack', 'myfavorite', 'myfavorite')

Note also:

  • You had what seems to be an unnecessary self.client.get(url) in the setUp(), which I’ve removed.
    If this is necessary because it sets a cookie or session data, then it should be added to check_register().
    Before this was only done for the first test, which may have been the cause of your failure.

  • Use example.com for test data, as it’s the domain explicitly registered for examples.
    email.com might be a real email provider, so if your tests accidentally sent emails, you’d sent potentially sensitive information to random accounts!

Hope this helps,

Adam

Hi Adam

Problem has been solved through parameterized, this package seems pretty much for my original intention. subtest() doesn’t work, still need to separate each test case for one test function.

Thanks for your informative advice, especially the concept about naming test function for different scenario and example.com information. Really appreciate. :grinning: