Django test RequestFactory vs Client - django

I am trying to decide whether I should use Django's Client or RequestFactory to test my views.
I am creating my server using DjangoRESTFramework and it's really simple, so far:
class SimpleModelList(generics.ListCreateAPIView):
"""
Retrieve list of all route_areas or create a new one.
"""
queryset = SimpleModel.objects.all()
serializer_class = SimpleModelSerializer
filter_backends = (IsOwnerFilterBackend,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
What are the differences between testing with Django's Client and RequestFactory and which approach is more suited for testing a REST server (if there is any difference besides liking one better)?
Should I create tests with both so as to provide a better coverage for my system?

RequestFactory and Client have some very different use-cases. To put it in a single sentence: RequestFactory returns a request, while Client returns a response.
The RequestFactory does what it says - it's a factory to create request objects. Nothing more, nothing less.
The Client is used to fake a complete request-response cycle. It will create a request object, which it then passes through a WSGI handler. This handler resolves the url, calls the appropriate middleware, and runs the view. It then returns the response object. It has the added benefit that it gathers a lot of extra data on the response object that is extremely useful for testing.
The RequestFactory doesn't actually touch any of your code, but the request object can be used to test parts of your code that require a valid request. The Client runs your views, so in order to test your views, you need to use the Client and inspect the response. Be sure to check out the documentation on the Client.

When using Django REST framework request factory would be helpfull to test the permissions.
EX:
Class TestPermission(TestCase):
def test_admin_permisiion(self):
admin_user = User.objects.create(email='admin#gmail.com',password='admin997',is_staff=True)
factory = RequestFactory()
request = factory.get('/')
request.user = admin_user
permission = IsAdminUser()
has_permission = permission.has_permission(request, None)
self.assertTrue(has_permission)
what we have done hear is we created a admin user by setting is_staff=True , then we created a request and assigned the admin as user of the request. request factory helps us do so. then we checked the IsAdminUser() permission from DRF against the request. the test will pass .
Client is to be used when you need to test the response returned by an Api.

Related

Django REST Framework - Set request in serializer test for ApiClient

I already read: Django REST Framework - Set request in serializer test?. And it doesn't work for me! Because I'm using APIClient and not RequestFactory like him.
I built a web app where the back-end is implemented using the Django REST Framework. Now I'm writing unit tests and I have come across a problem in testing my serializer methods. Here is one example of a serializer method I'm struggling with:
def get_can_edit(self, obj):
request = self.context['request']
user = User.objects.get(username=request.user)
return user == obj.admin
When trying to call this from the test, first I declare an instance of the serializer:
But now I need self.serializer to have the correct request when get_can_edit does self.context.get('request'). I've created a fake request with the correct information using APIClient:
self.client = APIClient()
self.client.force_authenticate(user)
conference = a_fake_conference
res = self.client.get('conference:conference-detail'. args=[conference.id])
serializer = ConferenceSerializer(conference, context={WHAT_IS_REQUEST?})
# I'm using a dict as context but the request gave me an error: context={'request': { 'user': user }}
sert.assertEqual(res.data, serializer.data)
Now I am stuck because I am unsure how to add request1 to serializer such that request = self.context['request'] will return
Thanks.
Use wsgi_request (source code-Django) of APIClient to get the WSGI request object.
self.client = APIClient()
self.client.force_authenticate(user)
res = self.client.get('conference:conference-detail'. args=[conference.id]
# make sure to call the `get(...)` method before accessing `wsgi_request` attribute
request_object = res.wsgi_request
Disclaimer: Not sure whether this is a DRF way to get the request object.
You're testing two things with each other here, so that's sort of part of the problem. Typically, you'd use self.client.get(...) as an integration test, that is, a test that ensures that everything from request to response is working as expected. For tests like these, you wouldn't use a serialiser, because then you're using your application code (your serialiser) to test itself.
If you're writing an integration test, you should be testing the raw response with something like:
from django.test import TestCase, RequestFactory
class MyUnitTestCase(TestCase):
def test_the_whole_stack(self):
response = self.client.get(
"conference:conference-detail", args=[conference.id]
)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.json()["some-key-you-expect"],
"Some value you expect"
)
Note that you also don't have to invoke APIClient() directly. It's there by default.
For a unit test, like the kind of test you'd write to make sure your serialiser is working properly, you don't need or want to be poking around with a WSGIRequest object. Instead, Django supplies a RequestFactory for just this case:
from django.test import TestCase, RequestFactory
class MyUnitTestCase(TestCase):
def test_my_serialiser(self):
serializer = ConferenceSerializer(
conference,
context={"request": RequestFactory().get("/")}
)
self.assertEqual(
serialiser.data["some-key-you-expect"],
"Some value you expect"
)

django test doesn't populate request.auth

I made an endpoint called /cars.
A person can create cars with a frontend, but devices read cars using an SDK, which has an API Key. This way, 2 rent-a-car companies can use the API without getting the cars mixed-up. Each app has its own API Key and its own person managing the contents.
This is being implemented with django restframework 3.x and django-oauth-toolkit.
I'm writing a test for a human retrieving cars, and another for a device.
This is failing:
def test_get_list(self):
# devices have a django user (AUTH_USER_MODEL ---onetoone--- Device)
self.client.force_authenticate(user=self.user_device)
self._get_list()
self.client.force_authenticate(user=None)
force_authentication sets request.auth to None. However, with postman or httpie, request.auth contains the Application object.
The queryset is:
def get_queryset(self):
if hasattr(self.request.user, 'device'):
# get the cars created by the owner of the API Key
return self.request.auth.application.user.cars.all()
return self.request.user.cars.all() # get my cars
Does this approach in the queryset make sense?
Am I testing it in the wrong way?
Why is request.auth empty? Is force_authentication using BasicAuthentication?
I would recommend going with check_object_permission for this kind of checks. You can read more here.
DRF documentation states that you need to force_authenticate the request if you are using APIRequestFactory. From the documentation:
from rest_framework.test import force_authenticate
factory = APIRequestFactory()
user = User.objects.get(username='olivia')
view = AccountDetail.as_view()
# Make an authenticated request to the view...
request = factory.get('/accounts/django-superstars/')
force_authenticate(request, user=user)
response = view(request)
To authenticate with APIClient try using credentials. Example from the documentation:
from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient
# Include an appropriate `Authorization:` header on all requests.
token = Token.objects.get(user__username='lauren')
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
The same is the second question.
As pointed in the documentation force_authenticate bypass authentication therefore it's your job to simulate the missing authentication part, including filling the request.auth.
Otherwise, you'll need to configure a data set and call either login or credential on the APIClient instance.

Django Rest Framework testing save POST request data

I'm writing some tests for my Django Rest Framework and trying to keep them as simple as possible. Before, I was creating objects using factory boy in order to have saved objects available for GET requests.
Why are my POST requests in the tests not creating an actual object in my test database? Everything works fine using the actual API, but I can't get the POST in the tests to save the object to make it available for GET requests. Is there something I'm missing?
from rest_framework import status
from rest_framework.test import APITestCase
# from .factories import InterestFactory
class APITestMixin(object):
"""
mixin to perform the default API Test functionality
"""
api_root = '/v1/'
model_url = ''
data = {}
def get_endpoint(self):
"""
return the API endpoint
"""
url = self.api_root + self.model_url
return url
def test_create_object(self):
"""
create a new object
"""
response = self.client.post(self.get_endpoint(), self.data)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data, self.data)
# this passes the test and the response says the object was created
def test_get_objects(self):
"""
get a list of objects
"""
response = self.client.get(self.get_endpoint())
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data)
# this test fails and says the response is empty [] with no objects
class InterestTests(APITestCase, APITestMixin):
def setUp(self):
self.model_url = 'interests/'
self.data = {
'id': 1,
'name': 'hiking',
}
# self.interest = InterestFactory.create(name='travel')
"""
if I create the object with factory boy, the object is
there. But I don't want to have to do this - I want to use
the data that was created in the POST request
"""
You can see the couple lines of commented out code which are the object that I need to create through factory boy because the object does not get created and saved (although the create test does pass and say the object is created).
I didn't post any of the model, serializer or viewsets code because the actual API works, this is a question specific to the test.
First of all, Django TestCase (APITestCase's base class) encloses the test code in a database transaction that is rolled back at the end of the test (refer). That's why test_get_objects cannot see objects which created in test_create_object
Then, from (Django Testing Docs)
Having tests altering each others data, or having tests that depend on another test altering data are inherently fragile.
The first reason came into my mind is that you cannot rely on the execution order of tests. For now, the order within a TestCase seems to be alphabetical. test_create_object just happened to be executed before test_get_objects. If you change the method name to test_z_create_object, test_get_objects will go first. So better to make each test independent
Solution for your case, if you anyway don't want database reset after each test, use APISimpleTestCase
More recommended, group tests. E.g., rename test_create_object, test_get_objects to subtest_create_object, subtest_get_objects. Then create another test method to invoke the two tests as needed

Can i access the response context of a view tested without the test client?

I have a function which i call from a unittest. From setting some debug traces i know the function worked like a charm and has all the values correctly prepared for return.
This is what my testcode looks like (see where my ipdb.set_trace() is ):
#override_settings(REGISTRATION_OPEN=True)
def test_confirm_account(self):
""" view that let's a user confirm account creation and username
when loggin in with social_auth """
request = self.factory.get('')
request.user = AnonymousUser()
request.session={}
request.session.update({self.pipename:{'backend':'facebook',
'kwargs':{'username':'Chuck Norris','response':{'id':1}}}})
# this is the function of which i need the context:
response = confirm_account(request)
self.assertEqual(response.context['keytotest'],'valuetotest')
From what i know from this part of the Django docs, i would be able to access response.context when i have used the testing client. But when i try to access response.context like i did it, i get this:
AttributeError: 'HttpResponse' object has no attribute 'context'
Is there a way to get the special HttpResponse object of the client, without using the client?
The RequestFactory does not touch the Django middleware, and as such, you will not generate a context (i.e. no ContextManager middleware).
If you want to test the context, you should use the test client. You can still manipulate the construction of the request in the test client either using mock or simply saving your session ahead of time in the test, such as:
from django.test import Client
c = Client()
session = c.session
session['backend'] = 'facebook'
session['kwargs'] = {'username':'Chuck Norris','response':{'id':1}}
session.save()
Now when you load the view with the test client, you'll be using the session as you set it, and when you use response = c.get('/yourURL/'), you can then reference the response context using response.context as desired.
The "response.context" is incorrect for new django versions but you can use response.context_data to get the same context that passed to TemplateResponse.
Though this is an old post, I suppose this tip can be of help. You can look into using TemplateResponse (or SimpleTemplateResponse) which can be substituted for render or render_to_response.
The Django docs has more on this
Yes, you can. You have to patch render.
I'm using pytest-django
class Test:
def context(self, call_args):
args, kwargs = call_args
request_mock, template, context = args
return context
#patch('myapplication.views.render')
def test_(self, mock_render, rf):
request = rf.get('fake-url')
view(request)
context = self.context(mock_render.call_args)
keytotest = 'crch'
assert keytotest == context['keytotest']
context (sic!) can be found in Response class. As you see it says it's HTTPResponse you get back from the view function. This happened because you've called it directly. Call this function via test client and it will be okay.
response = client.get('/fobarbaz/')
response.context

How do I test a middleware that requires sessions in Django properly?

I'm trying to figure out how to test middleware in django. The middleware I'm writing logs in a user under certain conditions (if a key sent in email is valid). So obviously I'm dependent on django.contrib.auth and django.contrib.sessions.
I'm running into problems testing the login portion. I'm making a request like this:
user = User.objects.create_user('user', 'user#example.org', 'password')
key = LoginKey.objects.create(user=user)
request = self.factory.get('/', data={'auth_key': key.hash}) # self.factory is a RequestFactory()
self.middleware.process_request(request) # self.middleware is MyMiddleware()
That fails due to the session not being set. So next, I wrote a little snippet in my test class:
def make_session(self, request):
SessionMiddleware().process_request(request)
and that fails due to 'User' object has no attribute 'backend'. I'm not sure on the meaning of that, but I suspect I need to run all the middlewares I have installed.
I don't really want to make a fake view for this just to run a middleware, but I can't see another option at this point.
So I just wanted to know, before I have to chase this rabbit all the way down the hole, is there a way of doing this that doesn't require as much duct tape?
You should use the test client for this. That will ensure that the middleware is run and the session keys created.
response = self.client.get('/?auth_key=%s' % key.hash)
self.assertTrue(response.context['user'].is_authenticated()) # for example