Django: Avoid HTTP API calls while testing from django views - django

I'm writing the tests for django views, Some of the views are making the external HTTP requests. While running the tests i dont want to execute these HTTP requests. Since during tests , data is being used is dummy and these HTTP requests will not behave as expected.
What could be the possible options for this ?

You could override settings in your tests and then check for that setting in your view. Here are the docs to override settings.
from django.conf import settings
if not settings.TEST_API:
# api call here
Then your test would look something like this
from django.test import TestCase, override_settings
class LoginTestCase(TestCase):
#override_settings(TEST_API=True)
def test_api_func(self):
# Do test here
Since it would be fairly messy to have those all over the place I would recommend creating a mixin that would look something like this.
class SensitiveAPIMixin(object):
def api_request(self, url, *args, **kwargs):
from django.conf import settings
if not settings.TEST_API:
request = api_call(url)
# Do api request in here
return request
Then, through the power of multiple inheritence, your views that you need to make a request to this api call you could do something similar to this.
class View(generic.ListView, SensitiveAPIMixin):
def get(self, request, *args, **kwargs):
data = self.api_request('http://example.com/api1')

This is where mocking comes in. In your tests, you can use libraries to patch the parts of the code you are testing to return the results you expect for the test, bypassing what that code actually does.
You can read a good blog post about mocking in Python here.
If you are on Python 3.3 or later, the mock library is included in Python. If not, you can download it from PyPI.
The exact details of how to mock the calls you're making will depend on what exactly your view code looks like.

Ben is right on, but here's some psuedo-ish code that might help. The patch here assumes you're using requests, but change the path as necessary to mock out what you need.
from unittest import mock
from django.test import TestCase
from django.core.urlresolvers import reverse
class MyTestCase(TestCase):
#mock.patch('requests.post') # this is all you need to stop the API call
def test_my_view_that_posts_to_an_api(self, mock_get):
response = self.client.get(reverse('my-view-name'))
self.assertEqual('my-value', response.data['my-key'])
# other assertions as necessary

Related

Query parameters in request on Django 3.0.3

I'm putting together an API and need to add query parameters to the URI like https://www.example.com/api/endpoint?search=term&limit=10.
My first question is, in Django 3.0, I'd need to use re-path to accomplish parsing out the various parameters, correct?
Secondly, the question is about convention. It seems like two of the three APIs I've been working with a lot lately us a convention like:
/api/endpoint?paramater1=abc&parameter2=xyz
Another uses something like:
/api/endpoint?$parameter1=abc&parameter2=abc
Looking at some past Django question related to this topic, I see stuff like:
/api/endpoint/?parameter1=abc&parameter2=xyz
Another post I read was suggesting that parameters should be separated with ;.
I guess I'm just curious what the "correct" convention should be either in terms of Django or general concensus.
Lastly, it seems to me what I'm trying to accomplish should be a GET request. The front-end sends the user defined parameters (section and startingPage) to the back-end where a PDF is generated matching those parameters. When it is generated, it sends it back to the FE. The PDFs are much too large to generate client-side. GET would be the correct method in the case, correct?
Well, I elected to go with the first convetion:
/api/endpoint?paramater1=abc&parameter2=xyz
Simply because the majority of APIs I work with use this convention.
For my files:
# urls.py
from django.urls import path
from .views import GeneratePDFView
app_name = 'Results'
urlpatterns = [
path('/endpoint',
GeneratePDFView.as_view(), name='generate_pdf')
]
# view.py
from django.conf import settings
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
from rest_framework.views import APIView
class GeneratePDFView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
if len(request.query_params) == 2:
data['id'] = {'id': request.user.id}
data['starting_page'] = request.query_params.get('parameter1')
data['data_view'] = request.query_params.get('parameter2')
serializer = GeneratePDFSerializer(data=data)
...

Bypass decorator with mock in django test

I am trying to write a simple test however my views are decorated with nested user_passes_test statements. They check things like a stripe subscription and is_authenticated. I have found various posts such as this which address how to bypass a decorator with patch but I can't quite work out how to integrate everything together.
tests.py
#patch('dashboard.views.authorised_base_user_checks', lambda func: func)
def test_dashboard_root_exists(self):
response = self.client.get('/dashboard/')
self.assertEqual(200, response.status_code)
decorator in views
def authorised_base_user_checks(view_func):
decorated_view_func = login_required(user_active(subscriber_exists(subscriber_valid(view_func))))
return decorated_view_func
views.py
#authorised_base_user_checks
def IndexView(request):
...
The above still fails to pass through the decorator.
Thanks!
This approach with patching of decorator most probably does not work because import of views module happens after the patching. If view has been already imported the decorator had been already applied to IndexView and patching the decorator function would have no effect at all.
You can reload the view module to overcome this:
import imp
import dashboard.views
#patch('dashboard.views.authorised_base_user_checks', lambda func: func)
def test_dashboard_root_exists(self):
# reload module to make sure view is decorated with patched decorator
imp.reload(views)
response = self.client.get('/dashboard/')
self.assertEqual(200, response.status_code)
# reload again
patch.stopall()
imp.reload(views)
Disclaimer: this code only demonstrates the idea. You need to make sure stopall and final reload always happens, so they should be in finally or in tearDown.

Is it possible to disable django haystack for some tests?

We use django-haystack as our search index. Generally great, but during tests it adds overhead to every model object creation and save, and for most tests it is not required. So I would like to avoid it. So I thought I'd use override_settings to use a dummy that did nothing. But I've now tried both the BaseSignalProcessor and the SimpleEngine and I can still see our search index (elasticsearch) getting hit a lot.
The two version I have tried are:
First using the SimpleEngine which does no data preparation:
from django.test import TestCase
from django.test.utils import override_settings
HAYSTACK_DUMMY_INDEX = {
'default': {
'ENGINE': 'haystack.backends.simple_backend.SimpleEngine',
}
}
#override_settings(HAYSTACK_CONNECTIONS=HAYSTACK_DUMMY_INDEX)
class TestAllTheThings(TestCase):
# ...
and then using the BaseSignalProcessor which should mean that the signals to save are not hooked up:
from django.test import TestCase
from django.test.utils import override_settings
#override_settings(HAYSTACK_SIGNAL_PROCESSOR='haystack.signals.BaseSignalProcessor')
class TestAllTheThings(TestCase):
# ...
I am using pytest as the test runner in case that matters.
Any idea if there is something I am missing?
The settings are only accessed once so overriding it after the fact won't change anything.
Instead, you can subclass the signal processor and stick in some logic to conditionally disable it like so:
from django.conf import settings
from haystack.signals import BaseSignalProcessor
class TogglableSignalProcessor(BaseSignalProcessor):
settings_key = 'HAYSTACK_DISABLE'
def handle_save(self, sender, instance, **kwargs):
if not getattr(settings, self.settings_key, False):
super().handle_save(sender, instance, **kwargs)
def handle_delete(self, sender, instance, **kwargs):
if not getattr(settings, self.settings_key, False):
super().handle_delete(sender, instance, **kwargs)
Now if you configure that as your signal processor then you can easily disable it in tests. The settings key can be set with an environment variable if you're just using manage.py test and not a custom runner. Otherwise you should know where to stick it.
import os
HAYSTACK_DISABLE = 'IS_TEST' in os.environ
And run it with
IS_TEST=1 python manage.py test
And for the few tests where you want it enabled, use override_settings() like you have already tried:
class MyTest(TestCase):
#override_settings(HAYSTACK_ENABLE=True)
def that_one_test_where_its_needed(self):
pass
Of course you can go even further and have conditional settings for the signal processor class so if you have a busy site then my conditional checks don't slow it down when it's running live.

How do I get the user in Django test?

I have some external services. My Django app is built on top of my external service APIs. In order to talk to my external service, I have to pass in an auth cookies, which I can get by reading User (that cookie != django cookies).
Using test tools like webtests, requests, I have trouble writing my tests.
class MyTestCase(WebTest):
def test_my_view(self):
#client = Client()
#response = client.get(reverse('create')).form
form = self.app.get(reverse('create'), user='dummy').form
print form.fields.values()
form['name'] = 'omghell0'
print form
response = form.submit()
I need to submit a form, which creates, say, a user on my external service. But to do that, I normally would pass in request.user (in order to authenticate my privilege to external service). But I don't have request.user.
What options do I have for this kind of stuff?
Thanks...
Suppose this is my tests.py
import unittest
from django.test.client import Client
from django.core.urlresolvers import reverse
from django_webtest import WebTest
from django.contrib.auth.models import User
class SimpleTest(unittest.TestCase):
def setUp(self):
self.usr = User.objects.get(username='dummy')
print self.usr
.......
I get
Traceback (most recent call last):
File "/var/lib/graphyte-webclient/webclient/apps/codebundles/tests.py", line 10, in setUp
self.usr = User.objects.get(username='dummy')
File "/var/lib/graphyte-webclient/graphyte-webenv/lib/python2.6/site-packages/django/db/models/manager.py", line 132, in get
return self.get_query_set().get(*args, **kwargs)
File "/var/lib/graphyte-webclient/graphyte-webenv/lib/python2.6/site-packages/django/db/models/query.py", line 341, in get
% self.model._meta.object_name)
DoesNotExist: User matching query does not exist
But if I test the User.objects in views, I am okay.
You need to use the setUp() method to create test users for testing - testing never uses live data, but creates a temporary test database to run your unit tests. Read this for more information: https://docs.djangoproject.com/en/dev/topics/testing/?from=olddocs#writing-unit-tests
EDIT:
Here's an example:
from django.utils import unittest
from django.contrib.auth.models import User
from myapp.models import ThisModel, ThatModel
class ModelTest(unittest.TestCase):
def setUp(self):
# Create some users
self.user_1 = User.objects.create_user('Chevy Chase', 'chevy#chase.com', 'chevyspassword')
self.user_2 = User.objects.create_user('Jim Carrey', 'jim#carrey.com', 'jimspassword')
self.user_3 = User.objects.create_user('Dennis Leary', 'dennis#leary.com', 'denisspassword')
Also note that, if you are going to use more than one method to test different functionality, you should use the tearDown method to destroy objects before reinstantiating them for the next test. This is something that took me a while to finally figure out, so I'll save you the trouble.
def tearDown(self):
# Clean up after each test
self.user_1.delete()
self.user_2.delete()
self.user_3.delete()
Django recommends using either unit tests or doc tests, as described here. You can put these tests into tests.py in each apps directory, and they will run when the command `python manage.py test" is used.
Django provides very helpful classes and functions for unit testing, as described here. In particular, the class django.test.Client is very convenient, and lets you control things like users.
https://docs.djangoproject.com/en/1.4/topics/testing/#module-django.test.client
Use the django test client to simulate requests. If you need to test the behavior of the returned result then use Selenium.

Django UrlResolver, adding urls at runtime for testing

I'm looking to do some tests and I'm not really familiar with the URLResolver quite yet but I'd like to solve this issue quickly.
In a TestCase, I'd like to add a URL to the resolver so that I can then use Client.get('/url/') and keep it separate from urls.py.
Since Django 1.8 using of django.test.TestCase.urls is deprecated. You can use django.test.utils.override_settings instead:
from django.test import TestCase
from django.test.utils import override_settings
urlpatterns = [
# custom urlconf
]
#override_settings(ROOT_URLCONF=__name__)
class MyTestCase(TestCase):
pass
override_settings can be applied either to a whole class or to a particular method.
https://docs.djangoproject.com/en/2.1/topics/testing/tools/#urlconf-configuration
In your test:
class TestMyViews(TestCase):
urls = 'myapp.test_urls'
This will use myapp/test_urls.py as the ROOT_URLCONF.
I know this was asked a while ago, but I thought I'd answer it again to offer something more complete and up-to-date.
You have two options to solve this, one is to provide your own urls file, as suggested by SystemParadox's answer:
class MyTestCase(TestCase):
urls = 'my_app.test_urls'
The other is to monkey patch your urls. This is NOT the recommended way to deal with overriding urls but you might get into a situation where you still need it. To do this for a single test case without affecting the rest you should do it in your setUp() method and then cleanup in your tearDown() method.
import my_app.urls
from django.conf.urls import patterns
class MyTestCase(TestCase):
urls = 'my_app.urls'
def setUp(self):
super(MyTestCase, self).setUp()
self.original_urls = my_app.urls.urlpatterns
my_app.urls.urlpatterns += patterns(
'',
(r'^my/test/url/pattern$', my_view),
)
def tearDown(self):
super(MyTestCase, self).tearDown()
my_app.urls.urlpatterns = self.original_urls
Please note that this will not work if you omit the urls class attribute. This is because the urls will otherwise be cached and your monkey patching will not take effect if you run your test together with other test cases.
Couldn't get it running with the answers above. Not even with the override_settings.
Found a solution which works for me. My usecase was to write some integration tests where I want to test put/post methods where I needed the urls from my app.
The main clue here is to use the set_urlconf function of django.urls instead of overwriting it in the class or using override_settings.
from django.test import TestCase
from django.urls import reverse, set_urlconf
class MyTests(TestCase):
#classmethod
def setUpClass(cls):
super().setUpClass()
set_urlconf('yourapp.urls') # yourapp is the folder where you define your root urlconf.
def test_url_resolving_with_app_urlconf(self):
response = self.client.put(
path=reverse('namespace:to:your:view-name'), data=test_data
)