'When testing Django REST Framework, why can't I get APIClient.credentials() to authenticate using a token?

I'm writing a functional (not unit) test against a basic API, like so:

from decouple import config
from rest_framework.test import APIClient, APITestCase


class ObjectAPIResponseTest(APITestCase):
    base_url = 'http://localhost:8000/api/objects/'
    token = config('LOCAL_API_TOKEN') # token stored in local .env file
    authenticated_client = APIClient()

    def setUp(self):
        self.authenticated_client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)

    def test_list_object_reaches_api(self):
        real_response = self.authenticated_client.get(self.base_url)

        self.assertEqual(real_response.status_code, 200)
        self.assertEqual(real_response.headers['content-type'], 'application/json')

The test fails: AssertionError: 401 != 200

After successfully testing the request with curl, I decided to try to load up requests with the Authorization header instead of using Django REST Framework's APIClient:

import requests

from decouple import config
from rest_framework.test import APIClient, APITestCase


class ObjectAPIResponseTest(APITestCase):
    base_url = 'http://localhost:8000/api/objects/'
    token = config('LOCAL_API_TOKEN') # token stored in local .env file

    authentication_header = {
        'Authorization': 'Token ' + token
    }

    def test_list_object_reaches_api(self):
        real_response = requests.get(self.base_url, headers=self.authentication_header)

        self.assertEqual(real_response.status_code, 200)
        self.assertEqual(real_response.headers['content-type'], 'application/json')

This passes, which makes me wonder if I'm somehow using APIClient.credentials() incorrectly, although what I've done seems basically in line with the documentation.

Any ideas what I'm doing wrong?

UPDATE

Inspired by Hafnernuss's comment, I wanted to see how the APIClient request was behaving, so I inspected the response from both the APIClient request and the requests request.

It looks like APIClient isn't actually hitting the API endpoint running on my development server, despite my explicitly requesting against its URL. If I remove the permission requiring a request to be authenticated and send a request through APIClient:

client_response = self.authenticated_client.get(self.base_url)
print(client_response.data)

I get this as response.data:

[]

I know that there's at least one object in my local database accessible via that endpoint, though. If I create a new object in the context of the test and run the request, I get that object returned:

[OrderedDict([('id', 3), ('property', 'foo')])]

So I think I have a fundamental misunderstanding of what's going on under the hood with APIClient. Is it somehow mocking out the request to my local development server's URL and requesting from a contrived endpoint that pulls from the test runner's database?



Solution 1:[1]

Here's an alternative solution to bypass API authentication:

In fact, in order to disable any kind of API auth (Token, Basic, etc) simply you can use the following decorator on top of your test_method():

from unittest.mock import patch

from <Respetive-App>.<Respective-Views> import <Respective-View>

@patch.object(<Respective-View>, "permission_classes", [])
def test_post_rate(self, api_client):
    payload = dict(content_id=self.content.id, score=5)
    response = api_client.post(
        "/api/endpoint/",
        payload,
    )
    assert response.status_code == 201

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