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)
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.
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.
@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:
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.
@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!
@xordoquy If you do end up wanting to change something, let me know if you need a helping hand :-)
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.
@dtheodor either forge the DRF request by yourself or use a Mock.
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.
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)
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.
@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
Most helpful comment
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: