Search code examples
djangodjango-unittestfactory-boy

Testing Django view requiring user authentication with Factory Boy


I need a view that allows staff users to view objects in a draft state. But I'm finding it difficult to write a unittest for this view.

I'm using Factory Boy for my setup:

class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User

    username = factory.LazyAttribute(lambda t: random_string())
    password = factory.PostGenerationMethodCall('set_password', 'mysecret')
    email = fuzzy.FuzzyText(
        length=12, suffix='@email.com').fuzz().lower()
    is_staff = True
    is_active = True

class ReleaseFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Release

    headline = factory.LazyAttribute(lambda t: random_string())
    slug = factory.LazyAttribute(lambda t: slugify(t.headline))
    author = factory.LazyAttribute(lambda t: random_string())
    excerpt = factory.LazyAttribute(lambda t: random_string())
    body = factory.LazyAttribute(lambda t: random_string())

class TestReleaseViews(TestCase):
    """
    Ensure our view returns a list of :model:`news.Release` objects.
    """

    def setUp(self):
        self.client = Client()
        self.user = UserFactory.create()
        self.client.login(username=self.user.username, password=self.user.password)

Given that I now have a logged-in, staff user for my tests, how do I go about using that to test against for a view (status_code 200 instead of 404)?

For instance, this test fails (404 != 200) when my view allows for users with is_staff as True to access the view:

def test_staff_can_view_draft_releases(self):
    "ReleaseDetail view should return correct status code"
    release = ReleaseFactory.create(status='draft')
    response = self.client.get(
        reverse(
            'news:release_detail',
            kwargs={
                'year': release.created.strftime('%Y'),
                'month': release.created.strftime('%b').lower(),
                'day': release.created.strftime('%d'),
                'slug': release.slug
            }
        )
    )
    self.assertEqual(response.status_code, 200)

Solution

  • Actually, you receive a 404 error because the self.client.login call fails.

    When you're passing password=self.user.password, you're sending the hash of the password, not the password itself.

    When you call UserFactory(), the steps taken by factory_boy in your factory are:

    1. Create an object with {'username': "<random>", 'is_active': True, 'is_staff': True, 'email': "<fuzzed>@email.com"}
    2. save() it
    3. Call user.set_password('my_secret')
    4. Call user.save() again

    By then, user.password is the result of set_password('my_secret'), not 'my_secret'.

    I'd go for (in your test):

    pwd = 'my_super_secret'
    self.user = UserFactory(password=pwd)
    self.client = Client()
    self.assertTrue(self.client.login(username=self.user.username, password=pwd))
    

    By the way, the declaration of your email field won't work as you expect it: when you write fuzzy.FuzzyText(...).fuzz().lower(), this gets executed only once, when the UserFactory class is declared.

    You should instead use factory.fuzzy.FuzzyText(chars='abcdefghijklmnopqrstuvwxyz', length=12, suffix='@example.com').