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')
Related
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')
I've currently got an endpoint that accepts GET requests, to retrieve users within a date range (there's no real need to explain the 'why'). The specific endpoint looks like this: GET /users/?fromdate={yyyy-mm-dd}&todate={yyyy-mm-dd}
For example, I can have a request such as: GET /users/?fromdate=2017-01-01&todate=2017-04-01
Without going too much into detail on the urls.py and views.py, I've specified the router register and path to accept requests to this endpoint (which works fine):
urls.py:
router = routers.DefaultRouter()
router.register(r"user", views.UserViewSet, basename='user')
urlpatterns = [
path('user/', views.UserViewSet.as_view({'get': 'user'}), name='user')
]
I am now trying to run a unit test just to send a request to this endpoint
Part of my test_user.py class looks like this:
def test_user_list(self):
response = self.client.get(reverse('user', kwargs={'fromdate': '2017-01-01', 'todate': '2017-04-01'})), format='json')
self.assertEqual(response.status_code, 200)
However, when I run the unit test, I get an error:
Reverse for 'user' with keyword arguments '{'fromdate': '2017-01-01',
'todate': '2017-04-01'}' not found. 1 pattern(s) tried: ['/user/$']
I think the error is due to the fact that my path (while it works manually through a REST client like Postman) doesn't specify the arguments to expect path('user/', views.UserViewSet.as_view({'get': 'user'}), name='user')
So how do I specify the arguments to satisfy a 'fromdate' and 'todate' as above? I have had a look at the documentation but I couldn't find anything other than urls such as /articles/2005/03/ for example.
kwargs, when passed to reverse(), become URL args, e.g. user/arg1/arg2/
If you want to pass URL vars, the kwargs should be passed to .get() instead, as data:
response = self.client.get(reverse('user'), {'fromdate': '2017-01-01', 'todate': '2017-04-01'})
Here is my code looks like :-
url.py file :-
from rest_framework import routers
from view_user import user_signup,user_login
router = routers.DefaultRouter()
urlpatterns = [
url(r'^api/v1/user_signup',csrf_exempt(user_signup)),
url(r'^api/v1/user_login',csrf_exempt(user_login))
]
view_user.py file:-
def user_signup(request):
try:
if request.method == 'POST':
json_data = json.loads(request.body)
return JsonResponse(result, safe=False)
except Exception as e:
logger.error("at method user : %s", e)
So, when I call the url:- http://myserver/api/v1/user_signup
it goes to "user_signup" method of view_user.py file.
But what I want is I should be able validate my request before it goes to the user_signup method.
I want this validation for all the requests that comes to my server for all methods (ex:- user_signup,user_login ...) before it goes to respective methods.
Annotate the concerned views with a decorator that contains the logic you want to execute before the views are called.
See Python - Decorators for a head start.
And How to write a custom decorator in django?
If you want to do this on all requests, regardless of the associated view, then you should consider writing a middleware. See how to setup custom middleware in django
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.
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.