Django testing how to assert Redirect - django

With the folliwing code I get this wrong result : nose.proxy.AssertionError: 302 != 200 : Couldn't retrieve redirection page '/mes_dossiers/': response code was 302 (expected 200)
what is wrong with my code ?
#test.py
from django.test import TestCase, RequestFactory, Client
from ..models import *
from ..views import *
from django.core.management import call_command
class Cas(TestCase):
def setUp(self):
call_command('loaddata', 'fixture_users.json', verbosity=1)
call_command('loaddata', 'xxxxx_tests_xxxx.yaml',
verbosity=1)
def test_dossier_duplicate(self) :
request = self.factory.get('/dossier/3/copier/', follow = True)
request.user = User.objects.get(id=3)
pk = 3
response = dossier_duplicate(request, pk)
response.client = Client()
self.assertRedirects(response,'/mes_dossiers/',status_code=302,
target_status_code=200)
#urls.py
urlpatterns = [
url(r'^dossier/(?P<pk>[0-9]+)/copier/$',views.dossier_duplicate),
]
#views.py
#login_required(login_url="/accounts/login/")
def dossier_duplicate(request, pk):
dossier = get_object_or_404(Dossier, pk=pk)
groupe = dossier.createdBy.groups.all()[0].name
if not in_group(request.user, groupe) :
return HttpResponseForbidden('Vous ne pouvez pas accéder à ce
dossier')
else :
#code to duplicate the "dossier" instance and child contents
#
#
return redirect('/mes_dossiers/')

I've found more examples there:
Django : Testing if the page has redirected to the desired url
https://docs.djangoproject.com/en/3.0/topics/testing/tools/#django.test.SimpleTestCase.assertRedirects
and this worked:
class Cas(TestCase):
def setUp(self):
call_command('loaddata', 'fixture_users.json', verbosity=1)
call_command('loaddata', 'xxx_tests_xxxx.yaml',
verbosity=1)
def test_dossier_duplicate(self) :
request = self.client.get('/dossier/3/copier/', follow = True)
request.user = User.objects.get(id=3)
pk = 3
response = dossier_duplicate(request, pk)
response.client = Client()
response.client.login(username='xxxx', password='xxxxx')
self.assertRedirects(response, '/mes_dossiers/', status_code=302,
target_status_code=200, fetch_redirect_response=True)

I reach this question via google and I have a similar issue with testing redirect.
But my Django is v2.2 and the accepted answer cites a v1.7 which is no longer supported
I then google some more and found this code block at https://docs.djangoproject.com/en/2.2/topics/testing/tools/#django.test.SimpleTestCase.settings
from django.test import TestCase
class LoginTestCase(TestCase):
def test_login(self):
# First check for the default behavior
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/accounts/login/?next=/sekrit/')
I modified for my own use which works.
For the OP case, this is how I believe should work if the OP uses 2.2
def test_dossier_duplicate(self) :
response = self.client.get('/dossier/3/copier/')
self.assertRedirects(response, '/mes_dossiers/')
I am leaving this here. In case future readers have a similar question but have it for Django v2.2

To test redirects you should use the test client instead of RequestFactory.
The API for the RequestFactory is a slightly restricted subset of the
test client API:
It only has access to the HTTP methods get(), post(), put(), delete(),
head(), options(), and trace().
These methods accept all the same
arguments except for follows. Since this is just a factory for
producing requests, it’s up to you to handle the response.
https://docs.djangoproject.com/en/1.11/topics/testing/advanced/#the-request-factory
Try changing self.factory.get to self.client.get
response = self.client.get('/dossier/3/copier/', follow = True)

Related

Testing urls django

I have a flatpage in my website. I created it by writing this:
urlpatterns = [path('admin/', admin.site.urls),path('facts/', include('django.contrib.flatpages.urls')),]
After that in admin page I created a page about-me, so the whole url for that page is ‘localhost/facts/about-me/’
I tried to write test for this page:
class StaticURLTests(TestCase):
def setUp(self):
self.guest_client = Client()
def test_about_author(self):
response = self.guest_client.get('/facts/about-me/')
self.assertEqual(response.status_code, 200)
But I get the following error:
self.assertEqual(response.status_code, 200)
AssertionError: 404 != 200
FAILED (failures=1)
Destroying test database for alias ‘default’…
Can’t figure out why. Maybe smb was facing the same problem.
I thought that the error was because of the flatpage but, the same error occured when I tried to test url of the Task model
urlpatterns = [path('task/<slug:slug>/',views.task, name='task'),]
Test:
#classmethod
def setUpClass(cls):
super().setUpClass()
Task = Task.objects.create(
title='Title',
slug=' test-slug',
description='Desc',
)
def setUp(self):
self.guest_client = Client()
user = get_user_model()
self.user = user.objects.create_user(username='Bob')
def test_task_url_exists_at_desired_location_authorized(self):
response = self.guest_client.get('/task/test-slug/')
self.assertEqual(response.status_code, 200)
I tried to change response = self.guest_client.get('/task/test-slug/') to response = self.guest_client.get(reverse('/task/')) but nothing seems to work.

Flask unittesting API requests

I am trying to write unit test cases for flas api server.
Can someeone please suggest ow to get rid of auth.login_required.
Tried mocking auth, but of no use.
with test_client its not hitting code block too.
api.py
from flask import Flask
from flask.ext.httpauth import HTTPBasicAuth
app = Flask(__name__)
auth = HTTPBasicAuth()
#app.route('/')
#auth.login_required
def index():
print "In index"
response.status_code = 200
return response
Tried following http://flask.pocoo.org/docs/0.12/testing/
from src.api import app
from unittest import TestCase
class TestIntegrations(TestCase):
def setUp(self):
self.app = app.test_client()
def test_thing(self):
response = self.app.get('/')
Can someone please help ??
There are two ways to do so - first is to disable authorization in tests:
// in your test module
from api import app, auth
import unittest
#auth.verify_password
def verify_password(user, password):
"""Overwrite password check to always pass.
This works even if we send no auth data."""
return True
Another approach is to actually send the auth headers from tests (this way you can also test your authorization system):
from api import app
from base64 import b64encode
import unittest
class ApiClient:
"""Performs API requests."""
def __init__(self, app):
self.client = app.test_client()
def get(self, url, **kwargs):
"""Sends GET request and returns the response."""
return self.client.get(url, headers=self.request_headers(), **kwargs)
def request_headers(self):
"""Returns API request headers."""
auth = '{0}:{1}'.format('user', 'secret')
return {
'Accept': 'application/json',
'Authorization': 'Basic {encoded_login}'.format(
encoded_login=b64encode(auth.encode('utf-8')).decode('utf-8')
)
}
class TestIntegrations(unittest.TestCase):
def setUp(self):
self.app = ApiClient(app)
def test_thing(self):
response = self.app.get('/')
print(response.data)
The ApiClient helper can also define post, delete methods which will be similar to get.
The full source code with examples is here.

Django View Testing Returning 301 or not found

I'm trying to test the response code of a view, but I'm either getting a 301 or does not exist.
urls.py
...
url(r'^myview/(?P<view_id>.*)/$', view_myview.index, name='myview'),
...
Test code 1:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get('/myview/123')
self.assertEqual(response.status_code, 200)
The above code gives:
AssertionError: 301 != 200
Test code 2:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get('/myview/123/')
self.assertEqual(response.status_code, 200)
The above code gives:
Mymodel matching query does not exist.
All I want to do is simple testing of my views to ensure they aren't throwing an error code, but I can't seem to find the right way to do it and I've tried many, many suggestions from the internets. Is there a different way to pass in view_id? What if I also want to throw in some query parameters?
EDIT: Updating to show the workaround I've used to accomplish what I'm trying to do, as horrible as it may be. I found that using dumpdata and fixtures took FOREVER.
from django.test import TestCase
from django.test import Client
import os
from . import urls_to_test # just a simple list of strings
class SimpleTest(TestCase):
""" Simply test if views return status 200 """
def setUp(self):
self.client = Client()
print('Dumping production database...')
os.system("sudo mysqldump mydb > /tmp/mydb.sql")
print('Loading production data into test database...')
os.system("sudo mysql test_mydb < /tmp/mydb.sql")
os.system("sudo rm -rf /tmp/mydb.sql")
def test_details(self):
for u in urls_to_test.test_urls:
print('Testing {}'.format(u))
response = self.client.get(u)
self.assertEqual(response.status_code, 200)
print('{} URLs tested!'.format(len(urls_to_test.test_urls)))
The first one doesn't work because Django is redirecting to the version with a final slash.
The second one tells you exactly why it doesn't work: you haven't created an item with id 123 - or indeed any items at all - within the test.
Consider creating object before testing its existance:
import unittest
from django.test import Client
from app.models import YourModel
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
self.obj = YourModel.objects.create(*your object goes here*)
def test_details(self):
response = self.client.get('/myview/123/') # It may be not /123/. It depends on how you generate url for model
self.assertEqual(response.status_code, 200)

Django or Django Rest Framework can't resolve url param when testing

I am using Django 1.8.4 and DRF 3.2.1 and when I run requests against the specified urls everything works fine, but when I run the tests with py.test the function update doesn't enter with the task_id param. But the url is fine.
Attached some code from urls.py and views.py and tests.py .. this is an excerpt of the code of course a lot of stuff is missing I just need eyes who can see if I am doing something wrong.
urls.py
from django.conf import settings
from django.conf.urls import include, patterns, url
from rest_framework import routers
from remotetask import views as rt_views
remotetask_detail = rt_views.RemoteTaskViewSet.as_view({'list': 'detail',
'put': 'update'})
remotetask_all = rt_views.RemoteTaskViewSet.as_view({'list': 'list'})
urlpatterns = patterns(
'',
url(r'^remotetasks/$', remotetask_all, name='api-remotetask-all'),
url(r'^remotetasks/(?P<task_id>\d+)/$', remotetask_detail,
name='api-remotetask-detail'),
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
)
views.py
from django.shortcuts import get_object_or_404
from rest_framework import generics
from rest_framework import status
from rest_framework.response import Response
from remotetask.models import RemoteTask
from remotetask.serializers import RemoteTaskSerializer
from rest_framework import viewsets
class RemoteTaskViewSet(viewsets.ViewSet):
queryset = RemoteTask.objects.all()
serializer_class = RemoteTaskSerializer
def detail(self, request, task_id=None):
task = get_object_or_404(RemoteTask, pk=task_id)
serializer = RemoteTaskSerializer(task)
return Response(serializer.data)
def update(self, request, task_id=None):
task = get_object_or_404(RemoteTask, pk=task_id)
new_status = request.data.get('status')
status_changed = task.change_status(new_status, stdout, stderr)
if status_changed:
response_status = status.HTTP_201_CREATED
else:
response_status = status.HTTP_400_BAD_REQUEST
serializer = RemoteTaskSerializer(task)
return Response(serializer.data, status=response_status)
And finally test_views.py
import pytest
from django.core.urlresolvers import reverse
from remotetask.factories import RemoteTaskFactory
from remotetask.models import RemoteTask
from remotetask.views import RemoteTaskViewSet
import json
#pytest.fixture()
#pytest.mark.django_db
def create_remotetask():
remotetask = RemoteTaskFactory.create()
return remotetask
#pytest.fixture()
#pytest.mark.django_db()
def clean_remotetask():
RemoteTask.objects.all().delete()
#pytest.fixture()
def rq_remotetasklist(rf):
url = reverse('api-remotetask-all')
request = rf.get(url)
response = RemoteTaskViewSet.as_view({'list': 'list'})(request)
return response
#pytest.mark.usefixtures('clean_remotetask', 'create_remotetask')
#pytest.mark.django_db
def test_remotetask_changestatus(rq_remotetasklist, rf):
response = rq_remotetasklist
result = response.data.get('results')
id_to_work = result[0]['id']
rt = RemoteTask.objects.get(pk=id_to_work)
assert rt.status == 0
# new request
url = reverse('api-remotetask-detail', kwargs={'task_id':id_to_work})
params = json.dumps({'status': 2, 'stdout': 'test', 'stderr': 'ok'})
request = rf.put(url, data=params,
content_type='application/json')
new_response = RemoteTaskViewSet.as_view({'put': 'update'})(request)
assert new_response.status_code == 200
By default when a new task is created it gets status 0, so I try to change the status to 2 and it fails, doing some debugging I found that is entering on the update function on RemoteTaskViewSet but is not getting the task_id.
I've followed lots of tutorials and changed back and forth the code and still having the same issue, luckily works in production but it worries to me that I cannot make it run test cases from this code.
The error output from py.test is this:
E assert 404 == 200
E + where 404 = <rest_framework.response.Response object at 0x7f9f465ae690>.status_code
I put a debugger into the update function, seems that task_id is None, but when I print request.stream the url is /api/remotetasks/1/ 1 should be the task_id but isn't getting it, I was about to open a ticket on djangoproject but I think isn't a django bug since it works with external client, this must be something on my code, or anything else.
Update: If I use client instead rf and comment the line where I assign new_response with the call of the method, and validate directly the against request.status_code it works!!!.
Something like this:
#pytest.mark.usefixtures('clean_remotetask', 'create_remotetask')
#pytest.mark.django_db
def test_remotetask_changestatus(rq_remotetasklist, client):
response = rq_remotetasklist
result = response.data.get('results')
id_to_work = result[0]['id']
rt = RemoteTask.objects.get(pk=id_to_work)
assert rt.status == 0
# new request
url = reverse('api-remotetask-detail', kwargs={'task_id': id_to_work})
params = json.dumps({'status': 2, 'stdout': 'test', 'stderr': 'ok'})
request = client.put(url, data=params,
content_type='application/json')
assert request.status_code == 201
Now the doubt is why it doesn't work in the previous way?
The issue (as noted in the updated) is in the request assignment:
request = rf.put(url, data=params,
content_type='application/json')
new_response = RemoteTaskViewSet.as_view({'put': 'update'})(request)
assert new_response.status_code == 200
There really is no need to do the custom call to the view. That's already being done by the request assignment. It's not working in the old test because that's not how the view gets called when it's routed through the url routes.
Most importantly in the pervious code the request object is not a request, it's the response to the call. Using the old code I believe this would have worked as well:
#pytest.mark.usefixtures('clean_remotetask', 'create_remotetask')
#pytest.mark.django_db
def test_remotetask_changestatus(rq_remotetasklist, rf):
response = rq_remotetasklist
result = response.data.get('results')
id_to_work = result[0]['id']
rt = RemoteTask.objects.get(pk=id_to_work)
assert rt.status == 0
# new request
url = reverse('api-remotetask-detail', kwargs={'task_id':id_to_work})
params = json.dumps({'status': 2, 'stdout': 'test', 'stderr': 'ok'})
response = rf.put(url, data=params,
content_type='application/json')
assert response.status_code == 200

Testing custom admin actions in django

I'm new to django and I'm having trouble testing custom actions(e.g actions=['mark_as_read']) that are in the drop down on the app_model_changelist, it's the same dropdown with the standard "delete selected". The custom actions work in the admin view, but I just dont know how to call it in my mock request, I know I need to post data but how to say I want "mark_as_read" action to be done on the data I posted?
I want to reverse the changelist url and post the queryset so the "mark_as_read" action function will process the data I posted.
change_url = urlresolvers.reverse('admin:app_model_changelist')
response = client.post(change_url, <QuerySet>)
Just pass the parameter action with the action name.
response = client.post(change_url, {'action': 'mark_as_read', ...})
Checked items are passed as _selected_action parameter. So code will be like this:
fixtures = [MyModel.objects.create(read=False),
MyModel.objects.create(read=True)]
should_be_untouched = MyModel.objects.create(read=False)
#note the unicode() call below
data = {'action': 'mark_as_read',
'_selected_action': [unicode(f.pk) for f in fixtures]}
response = client.post(change_url, data)
This is what I do:
data = {'action': 'mark_as_read', '_selected_action': Node.objects.filter(...).values_list('pk', flat=True)}
response = self.client.post(reverse(change_url), data, follow=True)
self.assertContains(response, "blah blah...")
self.assertEqual(Node.objects.filter(field_to_check=..., pk__in=data['_selected_action']).count(), 0)
A few notes on that, comparing to the accepted answer:
We can use values_list instead of list comprehension to obtain the ids.
We need to specify follow=True because it is expected that a successfull post will lead to a redirect
Optionally assert for a successful message
Check that the changes indeed are reflected on the db.
Here is how you do it with login and everything, a complete test case:
from django.test import TestCase
from django.urls import reverse
from content_app.models import Content
class ContentModelAdminTests(TestCase):
def setUp(self):
# Create some object to perform the action on
self.content = Content.objects.create(titles='{"main": "test tile", "seo": "test seo"}')
# Create auth user for views using api request factory
self.username = 'content_tester'
self.password = 'goldenstandard'
self.user = User.objects.create_superuser(self.username, 'test#example.com', self.password)
def shortDescription(self):
return None
def test_actions1(self):
"""
Testing export_as_json action
App is content_app, model is content
modify as per your app/model
"""
data = {'action': 'export_as_json',
'_selected_action': [self.content._id, ]}
change_url = reverse('admin:content_app_content_changelist')
self.client.login(username=self.username, password=self.password)
response = self.client.post(change_url, data)
self.client.logout()
self.assertEqual(response.status_code, 200)
Just modify to use your model and custom action and run your test.
UPDATE: If you get a 302, you may need to use follow=True in self.client.post().
Note that even if the POST is successful, you still need to test that your action performed the operations intended successfully.
Here's another method to test the action directly from the Admin class:
from django.contrib.auth.models import User
from django.contrib.admin.sites import AdminSite
from django.shortcuts import reverse
from django.test import RequestFactory, TestCase
from django.contrib.messages.storage.fallback import FallbackStorage
from myapp.models import MyModel
from myapp.admin import MyModelAdmin
class MyAdminTestCase(TestCase):
def setUp(self) -> None:
self.site = AdminSite()
self.factory = RequestFactory()
self.superuser = User.objects.create_superuser(username="superuser", is_staff=True)
def test_admin_action(self):
ma = MyModelAdmin(MyModel, self.site)
url = reverse("admin:myapp_mymodel_changelist")
superuser_request = self.factory.get(url)
superuser_request.user = self.superuser
# if using 'messages' in your actions
setattr(superuser_request, 'session', 'session')
messages = FallbackStorage(superuser_request)
setattr(superuser_request, '_messages', messages)
qs = MyModel.objects.all()
ma.mymodeladminaction(superuser_request, queryset=qs)
# check that your action performed the operations intended
...