Get current user in Django not working in tests

Viewed 467

I have a couple of views in my Django app that perform and action and record who did the action in the DB, something like:

def my_view(request):
    # do some stuff here first
    current_user = MyCustomUserObject.objects.filter(django_user_id=request.user.id).first()
    model_i_did_something_to_above.last_modified_by = current_user
    model.save()

And this actually works fine if I run the server and call it via postman. However, when I do a Unit test:

from datetime import datetime

class MyTests(TestCase):

    def setUp(self):
        now = datetime.utcnow()
        django_user = User.objects.create_user(username='myusername',
                                           email='test@test.com',
                                           password='abcd')
        
        self.user = MyCustomUserObject(user_name='myusername', email_address='test@test.com', created_datetime=now,
                    last_modified_datetime=now, is_admin=True, django_user=django_user)
        self.user.save()
        self.tokens = json.loads(self.client.post(reverse('authenticate'),
                                                  data={'username': 'myusername',
                                                        'password': 'abcd'},
                                                  content_type='application/json').content)

    def test_stuff(self):
        self.client.delete(reverse('nameoftheurl'),
                               {data: 'stuff'},
                               content_type='application/json',
                               **{'HTTP_AUTHORIZATION': self.tokens['access']})

And it reaches the view, it says that request.user.id is None. Why does this happen? Is there a way around this?

EDIT

The authenticate view goes like this:

from rest_framework_simplejwt.views import TokenObtainPairView

urlpatterns = [
    path('authenticate/', TokenObtainPairView.as_view(), name='authenticate'),
    ]

I also updated the test example above to show how the user is created.

2 Answers

The problem seems to be because your authorization header has the authorization type missing in your test_stuff test case.

It should be:

headers={'Authorization': f"Bearer {self.tokens['access']}"}

So:

    def test_stuff(self):
        self.client.delete(reverse('nameoftheurl'),
                               {data: 'stuff'},
                               content_type='application/json',
                               **{'HTTP_AUTHORIZATION': f"Bearer {self.tokens['access']}")
                                                       #  ^^^ Add this

You forgot to log your user in:

class MyTests(TestCase):

    ...

    def test_stuff(self):
        self.client.force_login(self.user)
        self.client.delete(
            reverse('nameoftheurl'),
            {data: 'stuff'},
            content_type='application/json',
            HTTP_AUTHORIZATION=self.tokens['access'],
        )

A snippet from the authentication docs:

If the current user has not logged in, this attribute will be set to an instance of AnonymousUser, otherwise it will be an instance of User.

An AnonymousUser has it's id set to None, hence your error.

On a side-note, this also indicates a bug in your delete view. You should check that a user is logged in before trying to log them out, otherwise you will end up returning a 500 error at some point (if not already).

Related