'How to test authenticated POST request with Pytest in Django

I want to test an authenticated post request on an API using Pytest. This is what I am doing so far:

 def test_auth_user_can_create(self, client):
      
    
    url = api_reverse('crud-simulation_api')

    data = {
        "project": "testproject",
        ....
    }

        response = client.post(url, json=data)
        assert response.status_code == 200

This doesn't work because it gives me back a 401 (Unauthorized) instead of a 200. That makes sense since the fixture is a client and not an admin client.

Yet if I pass in admin_client instead of client it gives me a Bad Request. The data that I send should be fine though.

I also tried to pass in the headers like so (since I use JWT authorization):

token = "bigassstringwhichismytoken"

headers = {
        "Authorization": "JWT " + token
    }

Finally I tried to log in before which gives me a 403 (Forbidden):

def test_auth_user_can_create_simulation_api(self, client, django_user_model):
     
    username = "Jack"
    password = "password"

    django_user_model.objects.create_user(username=username, password=password)
    client.login(username=username, password=password)

    url = api_reverse('crud-simulation_api')

    data = {
        "project": "testproject",
        ...
    }

    response = client.post(url, json=data)
    assert response.status_code == 200

If someone could point me into the right direction that would be fantastic! Thanks a lot in advance



Solution 1:[1]

You can hit the login url with username and password and get the token. creade a header dictionary like headers = {'Authorization': 'JWT <token>'}

and use the header when using post.

client.post(url, json=data, headers=headers)

Solution 2:[2]

To provide headers for client.{request} pass them individually as keyword agruments:

client.post(url, data, HTTP_FIRST_HEADER='...', HTTP_SECOND_HEADER='...')

Although you're unlikely to collide with any reserved parameter names in post call chain, better collect all headers you need in a dictionary:

headers = {
    'HTTP_FIRST_HEADER': '...',
    'HTTP_SECOND_HEADER': '...',
}

And pass them to request as arbitrary number of keyword arguments:

client.post(url, data, **headers)

In this case ** arguments are treated as extra information and are automatically added as headers.

Solution 3:[3]

I would suggest installing the pytest-django package. Based on its docs, the easiest answer would be just using the admin_client fixture. As admin_client has the type of django.test.Client, it can be used for both get and post requests.

def test_sth_with_auth(admin_client):
    response = admin_client.get('/private')
    assert response.status_code == 200

Also if you want to use a specific user, you can try sth like this:

@pytest.fixture
def my_user(django_user_model):
    return django_user_model.objects.create_user(username=username, password=password)

@pytest.fixture
def logged_in_client(client, my_user):
    return client.force_login(my_user)

def test_sth_with_auth(logged_in_client):
    response = logged_in_client.get('/private')
    assert response.status_code == 200

this part of the doc can be helpful to write your desired logged_in_client().

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Sam
Solution 2
Solution 3