I'm trying to verify if accessing routes of my Django application would return a 200 status code.
As I was writing repetitive test code, I search for some options and found that I could use RequestFactory with a mixin. But what I'm not understanding is why the test is not failing for a non existing route.
I have a class-based view called IndexView which I'm trying to test, but the idea is to use the mixin to test others as well.
The tests below will consider a non existing route called i-dont-exist:
Using client
What I see here is that I'm not using an instance of my view inside the test yet, but only the get to i-dont-exist route.
from django.test import TestCase, RequestFactory
from .. import views
class ExampleViewTest(TestCase):
def test_if_route_exists(self):
response = self.client.get('/i-dont-exist')
status_code = response.status_code
self.assertEqual(status_code, 200)
Running python3 manage.py test:
======================================================================
FAIL: test_if_route_exists (page.tests.test_views.ExampleViewTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "xxxxx/page/tests/test_views.py", line 15, in test_if_route_exists
self.assertEqual(status_code, 200)
AssertionError: 404 != 200
----------------------------------------------------------------------
Ran 1 test in 0.014s
FAILED (failures=1)
Using RequestFactory
Now I'm using my class-based view inside the test.
class ViewMixinTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
def get(self, view, url):
request = self.factory.get(url)
response = view.as_view()(request)
status_code = response.status_code
self.assertEqual(status_code, 200)
class OtherExampleViewTest(ViewMixinTest, TestCase):
def test_if_route_exists(self):
return self.get(views.IndexView, '/i-dont-exist')
Running tests again:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
I don't know why the pass is testing. I'm missing something?
Sorry, I'm new to tests.
Related
I am trying to implement unit tests to an existing project, the existing project uses Api-Key's to access and authenticate against the Api endpoints.
if I do the following via postman or command line:
curl --location --request GET 'http://127.0.0.1:8000/api/user_db' \
--header 'Authorization: Api-Key REDACTED' \
--header 'Content-Type: application/json' \
--data-raw '{
"username" : "test#testing.local"
}'
This will call the following view function and return the user details with the corresponding oid (json response) without error.
from django.shortcuts import render
from rest_framework_api_key.permissions import HasAPIKey
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from user_api.classes.UserController import (
GetBusinessUser,
CreateBusinessUser,
UpdateBusinessUser,
DeleteBusinesssUser
)
from celery.utils.log import get_task_logger
import environ
logger = get_task_logger(__name__)
env = environ.Env()
class ProcessUserRequest(APIView):
permission_classes = [HasAPIKey |IsAuthenticated ]
def get(self, request):
logger.info("Get Business User Request Received")
result = GetBusinessUser(request)
return Response(result["result"],
content_type='application/json charset=utf-8',
status=result["statuscode"]
This additionally calls the following shortened function:
def GetBusinessUser(request) -> Dict[str, Union[str, int]]:
logger.info(f"Processing Get Username Request: {request.data}")
valid_serializer = ValidateGetBusinessUserFormSerializer(data=request.data)
valid_serializer.is_valid(raise_exception=True)
username = valid_serializer.validated_data['username']
return BusinessUser.objects.filter(username=username).first()
As I wish to make unit test cases to ensure I can validate prior to deployment, I have implemented the following in the modules tests.py file:
from rest_framework.test import APITestCase, APIClient
from rest_framework_api_key.models import APIKey
from user_api.classes.UserController import GetBusinessUser
from django.urls import reverse
# Class Method for GetBusinessUser (truncated)
# try except handling and other user checks removed for stack
class ProcessUserRequestTest(APITestCase):
def setUp(self):
self.client = APIClient()
# have also tried: self.headers = {'HTTP_AUTHORIZATION': f'Api-Key {self.api_key.key}'}
self.client.credentials(HTTP_AUTHORIZATION='Api-Key SomeApiKeyValue')
self.url = reverse('business_user')
self.valid_payload = {'username': 'test#testing.local'}
self.invalid_payload = {'param1': '', 'param2': 'value2'}
def test_get_business_user_request(self):
# also tried based on above:
# response = self.client.get(self.url, **self.headers, format='json')
response = self.client.get(self.url, data=self.valid_payload, format='json')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data, GetBusinessUser(response.data).data)
No matter what I seem to do the following is always returned, so it appears from testing adding authentication headers or using the client.credentials does not work with Authorization: Api-Key somekey as a header?
creating test database for alias 'default'...
System check identified no issues (0 silenced).
{'detail': ErrorDetail(string='Authentication credentials were not provided.', code='not_authenticated')}
F
======================================================================
FAIL: test_get_business_user_request (user_api.tests.ProcessUserRequestTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "../truncated/tests.py", line 19, in in test_get_business_user_request
self.assertEqual(response.status_code, 200)
AssertionError: 403 != 200
----------------------------------------------------------------------
Ran 1 test in 0.018s
FAILED (failures=1)
Destroying test database for alias 'default'...
Has this been encountered before and is there a workable solution so I can create unit tests?
i'm currently testing my django (2.1.0) application with some api-unit-tests. I used the django rest framework(3.9.0) to build a login. For this i use code like this:
class LogoutTest(APITestCase):
def test_login_post_unauth(self):
response = requests.post('http://127.0.0.1:8000/myapp/user_info/')
self.assertEqual(response.status_code, 401)
def test_login_put_auth(self):
token = auth()
payload = {'Authorization': 'Token '+token}
response = requests.put('http://127.0.0.1:8000/myapp/user_info/', headers=payload)
self.assertEqual(response.status_code, 405)
def test_login_delete_auth(self):
token = auth()
payload = {'Authorization': 'Token '+token}
response = requests.delete('http://127.0.0.1:8000/myapp/user_info/', headers=payload)
self.assertEqual(response.status_code, 405)
The tests are running when i use:
coverage run --source='.' manage.py test myapp
like you see:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
....................................
----------------------------------------------------------------------
Ran 36 tests in 5.082s
OK
Destroying test database for alias 'default'...
But when i do coverage report i just get
myapp/auth.py 72 46 36%
Despite the fact my api test uses the code in my auth.py.
My auth.py looks like this:
def logout(request):
if request.method == 'GET':
request.user.auth_token.delete()
return JsonResponse({'message':'You are sucessfully logged out'}, status=200)
return JsonResponse({'error': 'Other Methods than GET not allowed'}, status=405)
But coverage says
return JsonResponse({'error': 'Other Methods than GET not allowed'}, status=405) will never be used.
Do you have an idea?
I found an answer. I wrote wrong tests. Django provides his own test tools and you should use them! And dont write something with requests cause coverage will identify your code in for example auth.py as not used cause your tests are not with the django test tools
I'm trying to run some tests to go with the Django REST tutorial (see source code). I got a solution working using the APIClient's .force_authenticate method, but I'd prefer to construct the credentials more explicitly. I've tried the following:
import json
import base64
from django.contrib.auth.models import User
from django.test import TestCase
from rest_framework.test import APITestCase, force_authenticate
from snippets.models import Snippet
class SnippetTestCase(APITestCase):
def setUp(self):
self.username = 'john_doe'
self.password = 'foobar'
self.user = User.objects.create(username=self.username, password=self.password)
# self.client.force_authenticate(user=self.user)
credentials = base64.b64encode(f'{self.username}:{self.password}'.encode('utf-8'))
self.client.credentials(HTTP_AUTHORIZATION='Basic {}'.format(credentials))
def test_1(self):
response = self.client.post('/snippets/', {'code': 'Foo Bar'}, format='json')
self.assertEqual(response.status_code, 201)
This test passed with the commented-out line with .force_authenticate, but fails in its current form, which I based on Using Basic HTTP access authentication in Django testing framework:
Kurts-MacBook-Pro:rest-framework-tutorial kurtpeek$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_1 (tutorial.tests.SnippetTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/kurtpeek/Documents/source/rest-framework-tutorial/tutorial/tests.py", line 23, in test_1
self.assertEqual(response.status_code, 201)
AssertionError: 403 != 201
----------------------------------------------------------------------
Ran 1 test in 0.022s
FAILED (failures=1)
Apparently, the authentication is not working because I'm getting a 403 Forbidden error. Any ideas on how to fix this?
try:
self.client.credentials(HTTP_AUTHORIZATION='Basic {}'.format(credentials.decode('utf-8'))
Alternatively, you may also consider this
I finally solved the issue by simply calling the self.client's login method with the username and password of the self.user defined in the setUp method:
import json
from django.contrib.auth.models import User
from django.test import TestCase
from rest_framework.test import APITestCase
from snippets.models import Snippet
class SnippetTestCase(APITestCase):
def setUp(self):
self.username = 'john_doe'
self.password = 'foobar'
self.user = User.objects.create_user(username=self.username, password=self.password)
def test_1(self):
self.client.login(username=self.username, password=self.password)
response = self.client.post('/snippets/', {'code': 'Foo Bar'}, format='json')
self.assertEqual(response.status_code, 201)
This test passes:
Kurts-MacBook-Pro:rest-framework-tutorial kurtpeek$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.259s
OK
Destroying test database for alias 'default'...
I want to write unit test, that proves that correct url will resolve to my view callable.
In this question Get Pyramid View callable by it's path (request context) I found the way to do it, but my test fails, probably that is because self.testing.scan() hasn't been run?
How can I reliably test that application will find correct view callable?
import unittest
from pyramid import testing
class ViewTests(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
def tearDown(self):
testing.tearDown()
# This one fails
def test_root_path_points_to_correct_view_callable(self):
from ..views import my_view
from pyramid.scripts.pviews import PViewsCommand
views_command = PViewsCommand([])
view = views_command._find_view(testing.DummyRequest(path='/'))
assert my_view is view
# This one passes
def test_my_view(self):
from ..views import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'my_project')
I am testing a set of Django applications which make extensive use of the 'permission_required' decorator. This derives in a 302 HTTP response in most of the views that I have.
My question is: there is any way to avoid or deactivate the 'permission_required' in testing, so I can get a 200 response when I call my views, instead of the 302?
Thank you!
Just log in with a superuser in your test case setUp method
from django.test import TestCase
from django.contrib.auth.models import User
class TestThatNeedsLogin(TestCase):
def setUp(self):
User.objects.create_superuser(
'user1',
'user1#example.com',
'pswd',
)
self.client.login(username="user1", password="pswd")
def tearDown(self):
self.client.logout()
def test_something(self):
response = self.client.get("/")
self.assertEqual(200, response.status_code)
You could monkey patch it:
import django.contrib.auth.decorators
real_permission_required = decorators.permission_required
# return a function that returns the exact function that was decorated, ignoring arguments
decorators.permission_required = lambda *args, **kwargs: lambda func: func
You need to make sure this happens before it's used, which is at the definition time of the object it's decorating. (For example, when that module is included.)
It also has to happen before it's rebound to another scope. After import django.contrib.auth.decorators is fine, but before from django.contrib.auth.decorators import permission_required.
Well. The solution I have found is to create a superuser in the setUp method from the TestCase class. I did it in that way:
def setUp(self):
self.client = Client()
self.user = User.objects.create_superuser(
'testuser',
'test#example.com',
'easy_password',
)
Then, when I want to test a URL, I do this:
def test_search_customers(self):
url = reverse('customer_search')
response = self.client.get(url)
# Not logged user. Must return a 302 HTTP code.
self.assertEquals(response.status_code, 302)
self.assertEquals(response['Location'], 'http://testserver/unauthorized/?next=/search/customers/')
# HERE I LOG IN MY SUPERUSER
self.client.login(username='testuser', password='easy_password')
response = self.client.get(url, follow=True)
# Same URL requested with a logged user with permissions. Must return 200 HTTP code.
self.assertEquals(response.status_code, 200)
This is what it worked for me :)
Thank you all. Cheers,
Jose