I'm trying to do my first tests on Django and I don't know do it or after reading the docs (where it explains a very easy test) I still don't know how do it.
I'm trying to do a test that goes to "login" url and makes the login, and after a succesfull login redirects to the authorized page.
from unittest import TestCase
from django.test.client import Client
class Test(TestCase):
def testLogin(self):
client = Client()
headers = {'X-OpenAM-Username': 'user', 'X-OpenAM-Password': 'password', 'Content-Type': 'application/json'}
data = {}
response = client.post('/login/', headers=headers, data=data, secure=False)
assert(response.status_code == 200)
And the test success, but I don't know if it's beacuse the 200 of loading "/login/" or because the test do the login and after redirect get the 200 code.
How can I check on the test that after the login the url redirected it's the correct? There is a plugin or something that helps with the test? Or where I can find a good tutorial to test my views and the model?
Thanks and regards.
To properly test redirects, use the follow parameter
If you set follow to True the client will follow any redirects and a
redirect_chain attribute will be set in the response object containing
tuples of the intermediate urls and status codes.
Then your code is as simple as
from django.test import TestCase
class Test(TestCase):
def test_login(self):
client = Client()
headers = {'X-OpenAM-Username': 'user', 'X-OpenAM-Password': 'password', 'Content-Type': 'application/json'}
data = {}
response = client.post('/login/', headers=headers, data=data, secure=False)
self.assertRedirects(response,'/destination/',302,200)
Note that it's self.assertRedirects rather than assert or assertRedirects
Also note that the above test will most likely fail because you are posting an empty dictionary as the form data. Django form views do not redirect when the form is invalid and an empty form will probably be invalid here.
Django have plenty of tools for testing. For this task, you should use test case class from Django, for example django.test.TestCase.
Then you can use method assertRedirects() and it will check where you've been redirected and with which code. You can find any info you need here.
I've tried to write the code for your task:
from django.test import TestCase
class Test(TestCase):
def test_login(self):
data = {'X-OpenAM-Username': 'user', 'X-OpenAM-Password': 'password'}
response = client.post('/login/', data=data, content_type='application/json', secure=false)
assertRedirects(response, '/expected_url/', 200)
Then you can use python3 manage.py test to run all tests.
Related
I'd like to build a request for testing middleware, but I don't want POST requests to always assume I'm sending form data. Is there a way to set request.body on a request generated from django.test.RequestFactory?
I.e., I'd like to do something like:
from django.test import RequestFactory
import json
factory = RequestFactory(content_type='application/json')
data = {'message':'A test message'}
body = json.dumps(data)
request = factory.post('/a/test/path/', body)
# And have request.body be the encoded version of `body`
The code above will fail the test because my middleware needs the data to be passed as the document in request.body not as form data in request.POST. However, RequestFactory always sends the data as form data.
I can do this with django.test.Client:
from django.test import Client
import json
client = Client()
data = {'message':'A test message'}
body = json.dumps(data)
response = client.post('/a/test/path/', body, content_type='application/json')
I'd like to do the same thing with django.test.RequestFactory.
RequestFactory has built-in support for JSON payloads. You don't need to dump your data first. But you should be passing the content-type to post, not to the instantiation.
factory = RequestFactory()
data = {'message':'A test message'}
request = factory.post('/a/test/path/', data, content_type='application/json')
I've tried Jay's solution and didn't work, but after some reseach, this did (Django 2.1.2)
factory = RequestFactory()
request = factory.post('/post/url/')
request.data = {'id': 1}
Here's what worked for me in Django 4.1:
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase, RequestFactory
from customauth import views
class RegistrationViewTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
def test_post_request_creates_new_user(self):
data = {
'email': 'new_user#email.com',
'screen_name': 'new_user',
'password1': 'new_user_password',
'password2': 'new_user_password',
}
request = self.factory.post('/any/path/will/do/', data )
middleware = SessionMiddleware(request)
middleware.process_request(request)
request.session.save()
response = views.registration_view(request)
self.assertEqual(response.status_code, 302)
# ok
This test passes. The form was successfully processed in views.registration_view.
Note:
When I included content_type='application/json' in the call to self.factory.post (as the accepted answer suggests), request.POST had no content in the view. Without that, it worked. I don't know why but would be happy to learn.
I needed to manually added SessionMiddleware to request.
In later version of Django (tested on 4.0) this is no longer an issue. On the other hand, to pass data to request.POST might be.
In default, when passing content-type to a RequestFactory, data goes into request.body and when you don't, data goes into request.POST.
request_factory = RequestFactory()
# provide content-type
request = request_factory.post(f'url', data={'foo': 'bar'}, content_type="application/json")
print(request.body) # b'{"foo": "bar"}'
# don't provide content type
request = request_factory.post(f'url', data={'foo': 'bar'})
print(request.POST) # <QueryDict: {'foo': ['bar']}>
Consider the following flow:
public client ----> DRF API on Service A ------> DRF API on Service B
Some of the DRF API on Service A merely proxying to Service B, so in the particular API on Service A looks like this:
class SomeServiceAPI(APIView):
def get(request):
resp = requests.get('http://service-b.com/api/...')
return Response(resp.json())
While this works on normal status, but it has a few issues:
It doesn't proxy the actual status code from service b.
Unnecessary round-trip of json serialization within Response()
If service b returns a non-json error, service does not return actual error from service b.
The question is, is there a better way to do it? I had a look at Django Rest Framework Proxy project, but I am not entirely sure if it actually suits my use case here.
You can solve the status code part by modifying your Response:
return Response(resp.json(), status=resp.status_code)
For the second part though, this is the essence of Proxying... (True, sometimes you want to manipulate the request and/or the response in the middleman of the proxy, but what you do is the essence).
Notes:
The DRF Proxy that you are suggesting seems to do the job just
fine, without the need for you to write a specific view just for the
roundtrip.
There exist another tool, DRF Reverse Proxy which is a DRF port of Django Revproxy and you may want to consider.
The general idea of both of the above is that you create a URL path specifically to Proxy the path to another API:
DRF Proxy:
Add your proxy to settings.py:
REST_PROXY = {
'HOST': 'http://service-b.com/api/'
}
In urls.py:
url(
r'^somewere_in_a/$',
ProxyView.as_view(source='somewere_in_b/'),
name='a_name'
)
DRF Reverse Proxy:
Pretty much similar with the above, without the settings part:
url(
r'^(?P<path>.*)$',
ProxyView.as_view(upstream='http://service-b.com/api/somewere_in_b/'),
name='a_name'
)
Opinion: the DRF Proxy seems more solid...
I had a look at both existing packages mentioned in John's answer but they don't seem to perfectly suit in my use case, so I have created a simple wrapper to proxy the requests' response to DRF response.
# encoding: utf-8
from __future__ import unicode_literals
from __future__ import absolute_import
from rest_framework.response import Response
from requests.models import Response as RResponse
class InCompatibleError(Exception):
pass
class DRFResponseWrapper(Response):
"""
Wraps the requests' response
"""
def __init__(self, data, *args, **kwargs):
if not isinstance(data, RResponse):
raise InCompatibleError
status = data.status_code
content_type = data.headers.get('content_type')
try:
content = data.json()
except:
content = data.content
super(DRFResponseWrapper, self).__init__(content, status=status, content_type=content_type)
And use as below:
resp = requests.get(
'{}://{}/api/v5/business/'.format(settings.SEARCH_HOST_SCHEMA, settings.SEARCH_HOST),
params=request.query_params
)
return DRFResponseWrapper(resp)
I have a modelForm as follows:
class UserProfileForm(forms.ModelForm):
class Meta:
model = FileUploads
fields = ['uploads']
and a view like so:
#csrf_exempt
def upper(request):
form = UserProfileForm(request.POST or None, request.FILES or None)
if form.is_valid():
form.save()
return HttpResponse(status=200)
return HttpResponse(status = 403)
And I have a simple script to send a multipart/encoded file to the view as such:
import requests
f = open('C:\\Users\\myname\\Desktop\\image.jpg', 'rb')
urls='http://localhost:8000/upper'
r=requests.post(urls, files= {'uploads':f})
print(r.status_code)
My question being: everything works fine as long as I have the csrrf_exempt decorator above the receiving view, that's fine for test environment. But what if I wanted the csrf protection in place? Considering the fact that I'm using requests module, how can I provide the csrf token?
You need to pass a cookie and a header with the same value:
import requests
f = open('C:\\Users\\myname\\Desktop\\image.jpg', 'rb')
urls='http://localhost:8000/upper'
cookies = {'csrftoken': 'token'}
headers = {'X-CSRF-TOKEN': 'token'}
r=requests.post(urls, files={'uploads':f}, cookies=cookies, headers=headers)
print(r.status_code)
The value of the token does not matter, you can take any literal, as long as they are the same.
I'm currently trying to test my API's endpoint using Django. So far I've used the APITestCase, but I'm having issues when it comes to login and using my token.
I have a Clark user with a password ('1'), I log him in, retrieve his token and add it to the header before sending my request. But I'm still getting a "Bad Credential Error".
connection_data = {'email':self.clark_user.email, 'password': '1'}
self.client.login(email=self.clark_user.email, password='1')
token = self.client.post(settings.SERVER_HOST+'/api/v1/auth/login', connection_data)
self.client.credentials(HTTP_AUTHORIZATION='Token ' + token.data['auth_token'])
response = self.client.patch(self.url_people, serializer.data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.client.logout()
My stack is the following:
Django rest framework (http://www.django-rest-framework.org/api-guide/testing/)
Djoser (https://github.com/sunscrapers/djoser)
And
from rest_framework.test import APITestCase
from core.test_utils import BaseTest For my tests.
Are the lib I use for my tests.
Django REST framework has APIClient class which serves this purpose. I think rather than getting Token over http, it can pulled out from DB directly. The code looks like
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=self.clark_user.username)
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
response = client.patch(self.url_people, serializer.data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
client.logout()
Documentation can be found at Testing Page.
I followed this code:
from django.core.urlresolvers import reverse
from rest_framework import status
from rest_framework.test import APITestCase
class AccountTests(APITestCase):
def test_create_account(self):
"""
Ensure we can create a new account object.
"""
url = reverse('account-list')
data = {'name': 'DabApps'}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data, data)
Found in the django-rest-framewok docs here:
DRF API-guide: Testing example
I created a single Model with a single field name, and I am still getting a "bad request 400 error". The view and reverse name is also set up correctly, and I have manually tested viewing the URL with success. I don't have Authentication enabled
And can't figure out if I am missing a step?
Does anyone have a working example of a django-rest-framework APITestCase create model object test code snippet?
This GIT repo has several working examples, which I was able to follow and get APITestCase working:
django-rest-framework-oauth2-provider-example/apps/users/tests.py
It could be a JSON decode error.
In the line self.client.post(url, data, format='json') use json.dumps(data) and try.