Thanks to visit codestin.com
Credit goes to github.com

Skip to content

APIRequestFactory does not return a rest_framework.request.Request object #3608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jorisroovers opened this issue Nov 6, 2015 · 14 comments
Closed

Comments

@jorisroovers
Copy link

The APIRequestFactory class is basically a wrapper for Django's RequestFactory, proxying post/get/put/... calls to Django's RequestFactory.generic(). As a result, the mock request objects that are returned by APIRequestFactory get/post/put/... methods are of the type django.core.handlers.wsgi.WSGIRequest. When passed to a django-rest-framework view, these requests get converted to rest_framework.request.Request objects. While this probably suffices for many use cases, this does cause issues when trying to test code that handles requests directly without going through a django-rest-framework view.

In particular, if the code that is being tested tries to access special data members or methods that are specific to rest_framework.request.Request such as the data member, that code will raise an exception,

For example:

  def test_foo(self):

        def myfunc(request):
            # This will raise an exception since the request is of type 
            # django.core.handlers.wsgi.WSGIRequest
            # and not of type rest_framework.request.Request
            # -> AttributeError: 'WSGIRequest' object has no attribute 'data'
            print request.data

        from rest_framework.test import APIRequestFactory
        factory = APIRequestFactory()
        request = factory.post("/foo", {'key': "val"})
        myfunc(request)
@xordoquy
Copy link
Collaborator

xordoquy commented Nov 6, 2015

If your view doesn't go through the DRF view, you won't have a DRF's request. You should therefore not use anything specific to DRF but fallback to Django's default for both your view and the request factory.

@xordoquy xordoquy closed this as completed Nov 6, 2015
@jorisroovers
Copy link
Author

In this case, I'm trying to test a function that handles a request that is normally passed to it via a DRF view. In my unit tests, I'd like to be able to skip the view part (that also deals with serialization and other things) and just test the function with a specific request input so that I can do granular tests of just that piece of functionality (i.e. a unit test). I understand the principle of "if it doesn't go through a DRF view, then don't use DRF features", but I do think it's at least a little unexpected that DRF's APIRequestFactory doesn't return a DRF request.

Since this issue is already closed, I assume you have a strong opinion about this and consider it a non-issue.

For future reference, I just ended up monkey patching the requests returned by APIRequestFactory so that they behave like DRF requests for the fields I need.

@xordoquy
Copy link
Collaborator

xordoquy commented Nov 6, 2015

@jorisroovers APIFactory returns a django request because it'll go through the DRF processes of authentication, permissions, content management and so on which will turns the request into a DRF's one.
If you want to unittest a function that would have use DRF request, I'd probably:

  • remove the request from the arguments and make it so that only the meaningful part are passed to the function (usually, it'll be the data, sometime a bit more).
  • use a mock
  • forge a fake DRF request directly

@xordoquy
Copy link
Collaborator

xordoquy commented Nov 6, 2015

On a side note, the strong opinion was based on the initial input where I understood you were trying to use a DRF request with a Django regular view.
May reconsider depending on the use case though.
Considering writing a bit more in the documentation about APIRequestFactory returning a Django request too.

@jorisroovers
Copy link
Author

@xordoquy Thanks for clarifying :-)
We actually passed the specific data first but ended up changing that... Regardless, we've got a workaround in place, thanks for the quick replies!

@jorisroovers
Copy link
Author

@xordoquy If you do end up wanting to change something, let me know if you need a helping hand :-)

@dtheodor
Copy link

I also thought that the purpose of APIRequestFactory is to 'forge a fake DRF request directly' as you put it.

How am i supposed to unittest a decorator that works on api_view functions? These functions receive a DRF request object.

@api_view(...)
@my_decorator
def view(request):
    # ....

Ok, I could create some generic mock.Mock but there is already a function that serves the purpose of creating a good Request object mock: django's RequestFactory , and your APIRequestFactory should mirror its purpose imo.

@xordoquy
Copy link
Collaborator

@dtheodor either forge the DRF request by yourself or use a Mock.

@tomchristie
Copy link
Member

To convert into a Request object, see: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/views.py#L361

We don't have any very good options here, given views expect an HTTPRequest object.

@cesarcruz
Copy link

I got this problem in my tests, then inspecting the response object returned in the view and I find the DRF's Request object, for the record I created a new example based on @jorisroovers's code, so it could helps others:

def test_foo(self):
    def myfunc(request):
        # This will raise an exception since the request is of type 
        # django.core.handlers.wsgi.WSGIRequest
        # and not of type rest_framework.request.Request
        # -> AttributeError: 'WSGIRequest' object has no attribute 'data'
        print request.data

    from rest_framework.test import APIRequestFactory
    factory = APIRequestFactory()
    request = factory.post("/foo", {'key': "val"})  # WSGIRequest

    # Add the view call to get response object
    view = MyView.as_view(actions={'post': 'detail'})
    response = view(request)
    response.render()
    drf_request = response.renderer_context['request']  # rest_framework.request.Request

    myfunc(drf_request)

@ironyinabox
Copy link

ironyinabox commented Feb 23, 2018

I'm having a (possibly) related issue, where my code expects request.data to be a dict, but when I try to unit-test using as_view(#args#) and APIRequestFactory, request.data is a QueryDict. Not sure which piece of the puzzle I'm missing here.

@anentropic
Copy link

anentropic commented Sep 20, 2018

@ironyinabox I think that is not related to this issue at all

if you are sending form-encoded data then you should expect a QueryDict

if you were expecting a dict because you are trying to send a JSON object then the fact you got back a QueryDict suggests that the content type of your request is not set properly, or your DRF view is not set to accept/parse JSON

@ramast
Copy link

ramast commented Jun 22, 2021

I have the same issue:
I am trying to unit test a custom rest framework permission class.

For testing purposes I initialize permission class instance manually and test has_permission and has_object_permission with different request data.

that of course fails because request has no attribute data.

@mesajidiqbal
Copy link

To resolve this issue, you need to wrap the APIRequestFactory request with rest_framework.request.Request. This will provide the necessary context and attributes expected in DRF views, including the data attribute.

Here's an example of how to correctly use APIRequestFactory for both GET and POST requests:

# Imports
from rest_framework.test import APIRequestFactory
from rest_framework.request import Request
from rest_framework.parsers import JSONParser
import json

# Initialize the APIRequestFactory
request_factory = APIRequestFactory()

# Query Param or Data
data = {"key": "value"}

# Example for a GET request
request = request_factory.get('/api/url/', data)
request = Request(request)

# Example for a POST request
json_data = json.dumps(data)  # data must be converted to string for POST requests
request = request_factory.post('/api/url/', data=json_data, content_type='application/json')
request = Request(request, parsers=[JSONParser()])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants