django.test.client got 404 error on existing url - python

I just started learning unittests and stuck with this problem.
I got project structure like this (it’s Django 1.6.2 now):
./manage.py
./myproject
./myproject/urls.py
./myproject/myapp/
./myproject/myapp/urls.py
./myproject/myapp/views.py
./tests/
./test/test_example.py
In the ./myproject/urls.py I have:
from django.conf.urls import patterns, include, url
urlpatterns = patterns('',
url(r'^myapp/', include('myproject.myapp.urls')),
)
In the ./myproject/myapp/urls.py I have:
from django.conf.urls import patterns, url
urlpatterns = patterns('myproject.myapp.views',
url(r'^example1/$', 'itemlist'),
url(r'^example1/(?P<item_id>\w+)/$', 'item'),
)
I wrote basic test and put it into ./test/test_example.py
import unittest
from django.test import Client
class PagesTestCase(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_itemlist(self):
response = self.client.get('/myapp/example1/')
self.assertEqual(response.status_code, 200)
def test_item(self):
response = self.client.get('/myapp/example1/100100/')
self.assertEqual(response.status_code, 200)
I run this tests from shell like this:
cd ./tests
python manage.py test
First test runs OK, but he second always fails with ‘404 not found’ status code.
Both urls are working OK in the browser.
Also, I tried this:
cd ./
python manage.py shell
>>> from django.test.client import Client
>>> c = Client()
>>> r = c.get('/myapp/example1/100100/')
>>> r.status_code
200
I just can’t figure out how to run those tests properly. It seems no pattern that is passed into views as parameter ever works for me. But all fixed urls are found correctly by django.test.client.
Thank you!
EDIT: I just found that 404 fires in my myproject/myapp/views.py
There is a code:
def item(request, item_id):
try:
item = Item.objects.get(pk = int(item_id))
except (ValueError, Item.DoesNotExist):
raise Http404
And here goes the Item.DoesNotExist exception. I have no any idea, why that item not found?

Use the reverse() function instead to build URLs, that is:
In ./myproject/myapp/urls.py file give each URL pattern a name parameter that is for example:
from django.conf.urls import patterns, url
urlpatterns = patterns('myproject.myapp.views',
url(r'^example1/$', 'itemlist', name='example-one'),
url(r'^example1/(?P<item_id>\w+)/$', 'item', name='example-two'),
)
We will use the value given to the name parameter to build URLs.
Then in ./test/test_example.py:
from django.core.urlresolvers import reverse
class PagesTestCase(unittest.TestCase):
...
def test_itemlist(self):
url = reverse('example-one')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
def test_item(self):
url = reverse('example-two')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
That should do the trick.

In addition to using reverse, you can get a 404 if the expected model isn't available in the test database (as mentioned in one of the comments). You should also use Django's TestCase instead of python's unittest since the former inherits from the latter, but makes interacting with the database much easier (among other things).
An example that sets up test data:
from django.test import TestCase
from django.urls import reverse
# Or whatever your object is.
from .models import Item
class ItemViewsTestCase(TestCase):
"""Tests for Item views."""
#classmethod
def setUpTestData(cls):
"""Set up test data available for all tests in this class."""
cls.item = Item.objects.create(name='Testing')
def test_item_list_view(self):
# Note, self.client we get for free from Django's TestCase.
response = self.client.get(reverse('itemlist'))
self.assertEqual(response.status_code, 200)
def test_item_detail_view(self):
# This url pattern expects an Item.id so use the item we set up.
url = reverse('item', args=[self.item.id])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)

Related

does it make sense to use django urls for view test?

im new to django and im working on a webesite and i want to write unit tests for it and im kind of confused how to do it.
does it make any sense to get urls from django its self and test their views?
from django.test import TestCase
from django.urls import get_resolver
from kernel.settings.base import TEMPLATES_DIR
class ViewTest(TestCase):
Site_urls= list(set(v[1] for k,v in get_resolver().reverse_dict.items()))
Site_urls= [i.replace('\\','') for i in Site_urls]
Site_urls= [i.replace('/','') for i in Site_urls]
Site_urls= [i.replace('$','') for i in Site_urls]
def urlexists(self):
for i in self.Site_urls:
with self.subTest(line= i):
response = self.client.get(f'/{i}/')
self.assertEqual(response.status_code, 200)
my url examples:
urlpatterns = [
path('about/', aboutView.as_view(),name='About'),
]
also other tests like if view uses correct template which acquires url names...
so is this the correct way of doing it or should i use static files?
Yes, if you want to check if the url correspond to the right view.
here is a example
url correspond to the right view
from .views import register_view
from django.urls.base import resolve
class RegisterTests(TestCase):
def test_register_view(self):
view = resolve('/account/register')
self.assertEqual(
view.func.__name__,
register_view.__name__,
)
When you run python manage.py test, it should passed if you set the right view.
url correspond to the right template
use reverse and client.get to visit your url.
from django.urls.base import resolve, reverse # <--- new
from .views import register_view
class RegisterTests(TestCase):
def test_register_view(self):
...
def test_register_template(self): # <--- new
url = reverse('account:register')
self.response = self.client.get(url)
self.assertEqual(self.response.status_code, 200)
self.assertTemplateUsed(self.response, 'account/register.html')

AttributeError: 'UserViewSet' object has no attribute 'user'

I've currently got an API endpoint that works as I would expect it when I send a request to it manually via Postman. The way I do so is running:
GET /user/?fromid=1&toid=100
I am now trying to setup unit tests using factory-boy but I'm not able to get it to work due to an error.
So far, the important files with regards to the unit tests that I have are:
urls.py
from django.urls import include, path
from rest_framework import routers
from rest_framework.schemas import get_schema_view
import views
router = routers.DefaultRouter()
router.register(r"user", views.UserViewSet, basename='user')
urlpatterns = [
path('user', views.UserViewSet.as_view({'get': 'user'}), name='user')
]
test_user.py
import factory
from django.test import Client, TestCase
from django.urls import reverse
from factory import DjangoModelFactory, Faker
from models.user import User
class UserFactory(DjangoModelFactory):
class Meta:
model = User
class UserViewSetTest(TestCase):
def setUp(self):
client = Client()
def test_user_list(self):
response = self.client.get(reverse('user'), format='json')
self.assertEqual(response.status_code, 200)
When running pytestusing the above, I get an error:
AttributeError: 'UserViewSet' object has no attribute 'user'
I have a feeling that the parameters in the URL aren't allowing me to call it as is from my unit test, but I'm not sure.
As I mentioned, I've been able to send a request via Postman to the API so I'm not sure it's useful to show my complete code (i.e. the ViewSet, models and serializers). For what it's worth, the viewset currently just returns a list of users in Json format.
EDIT:
I've been able to recreate the error manually on Postman. If I create a request without specifying the URL parameters, like the following:
GET /user
I get the same error manually
AttributeError: 'UserViewSet' object has no attribute 'user'
As a result, I have tried to update my reverse command within the unit test to include the URL parameters, as such:
response = self.client.get(reverse('user'), kwargs={'fromid':1, 'toid': 100}, format='json')
But I'm still getting the same original error.
EDIT2:
I think part of the problem is with the way I'm calling reverse. The kwargs parameter wasn't in the actual reverse function. I've modified it to be as such:
response = self.client.get(reverse('user', kwargs={'fromid':1, 'toid': 100}), format='json')
Now my error is to do with the url path (I believe) as now I'm getting a new error which is:
Reverse for 'user' with keyword arguments '{'fromid':1, 'toid': 100}'
not found. 1 pattern(s) tried: ['/user$']
My (hopefully final) question is how do I specify multiple parameters in my path to accept the kwargs? My endpoint is currently accepted to parameters (fromid and toid) in the URL, so I need to specify that in the path elements below. How can I do so?
router.register(r"user", views.UserViewSet, basename='user')
urlpatterns = [
path('user', views.UserViewSet.as_view({'get': 'user'}), name='user')
]
drf routers will use a name like
basename+'-list'
for generated url with list actions. so your code should be:
response = self.client.get(reverse('user-list', kwargs={'fromid':1, 'toid': 100}), format='json')

How to print the path of the html file in response in Django test

I want to know which template file django is reading when I'm testing.
I use MEZZANINE for my website. I tried to change the home page. But I'm not sure which file to change.
from django.test import TestCase
import os
# Create your tests here.
class ProjectTests(TestCase):
def test_homepage(self):
response = self.client.get('/')
#print dir(response)
print response.template_name
self.assertEqual(response.status_code, 200)
I expect to print the absolute path of response.template_name. Thanks.

Django Integration tests for urls

I have a django app I wanted to write tests for. For now Im writing integration tests for the urls.
For my signin test , my url looks like:
url(r'^signin/$', login_forbidden(signin), name='signin')
and my test looks like:
from django.test import TestCase
class SigninTest(TestCase):
def test_signin(self):
resp = self.client.get('/signin/')
self.assertEqual(resp.status_code, 200)
However I have no idea to test a a longer url, for instance I have one entry in urls like:
url(
r'^ad_accounts/(?P<ad_account_id>[^/]+)/$',
AdAccountDetailView.as_view(),
name='campaigns'
),
If I repeat the above test I have for the signin page (replacing resp = self.client.get('/ad_accounts/')) returns a failure
======================================================================
FAIL: test_signin (engineoftravel.tests.SigninTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "path/to/project/tests.py", line 7, in test_signin
self.assertEqual(resp.status_code, 200)
AssertionError: 302 != 200
----------------------------------------------------------------------
Ran 1 test in 0.103s
FAILED (failures=1)
why not use reverse: https://docs.djangoproject.com/en/1.10/ref/urlresolvers/#reverse
from django.core.urlresolvers import reverse
....
resp = self.client.get(reverse('campaigns', args=[1]))
where args is the id you need to pass in.
EDIT: since django 1.10 reverse imports from django.urls
from django.test import TestCase
from django.test import Client
from django.core.urlresolvers import reverse
client = Client()
class MainTest(TestCase):
##user login in django
def user_login(self, username, password):
response = self.client.login(username=username, password=username)
return self.assertEqual(resp.status_code, 200)
## first case
def detail(self):
response = self.client.get('/ad_accounts/<test_num>/') ## url regex
self.assertEquals(response.status_code, 200)
def detail2(self):
response = self.client.get(reverse('campaigns'))
self.assertEqual(response.status_code, 200)
For the test failure, you should first login with some test user then make a request, otherwise the page will be redirected to the login page and thus you will get a 302 status code.
Also you can test the status code of the redirected page with client.get('/foo', follow=True) which returns the status code of the sign in page. (In your example)

Django, Testing Redirects to External Pages

I have a routing rule in my Django app for downloads that redirect to a external CDN. I am now writing tests for my app, and I want to test that the route does successfully redirect to the configured url in my Django settings. Here is a simplified example that should help explain what I'm trying to do:
from django.test import SimpleTestCase
from django.test.client import Client
from django.conf import settings
class MyTestCase(SimpleTestCase):
def setUp(self):
self.client = Client()
def test_download(self):
response = self.client.get('/download/mac/')
self.assertRedirects(response, settings.URLS.get('mac'))
Now this doesn't work, the redirect gets a 404 even though when I print settings.DOWNLOAD_URL in this method it is correct, and a copy/paste into the browser proves it works. I started to look into why it wasn't working, and I noticed this in the Django source code:
Note that assertRedirects won't work for external links since it uses
TestClient to do a request.
So then, how does one test these redirects? I'm not looking for anything super fancy, what I expect to check is the response's status_code and location. I saw that response has a follow parameter, and tried something like this, but it still didn't work:
def test_download(self):
response = self.client.get('/download/mac/', follow=True)
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], settings.URLS.get('mac')
It was requested that I include the relevant parts from my urls.py and views.py, here they are:
# urls.py
from django.conf.urls import patterns, url
urlpatterns = patterns('myapp.views',
url(r'^download/(?P<platform>\w+)/$', 'download_app', name='download'),
)
#views.py
from django.conf import settings
from django.shortcuts import redirect
from django.http import Http404
def download_app(request, platform):
if platform in settings.URLS:
return redirect( settings.URLS.get(platform) )
else:
raise Http404
Any help in solving this would be much appreciated. Thanks in advance.
Probably the original poster has solved his problems and moved on long ago, but the solution here is to not specify follow=True as he did in the alternative proposal that didn't work either.
The following will simply check that the view being tested redirects as expected, without relying on the external resources.
def test_download(self):
response = self.client.get('/download/mac')
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], 'http://external/resource')
In Django 1.7 assertRedirect got a new parameter fetch_redirect_response which can be set to False to get the same effect, but I haven't tried it myself.

Categories