what is the best way to test modules in views? - django

I have gone through this post, but it didn't contain any relevant answer.
I am using Django 1.11 , and my views.py is modular (not class based).
I want to test views modules (functions) in the shell, in django's python shell.
>>> python manage.py shell
By directly importing views like:
>>> from my_app import views
It works, but this doesn't seem to be preferred way to me.
Is there any preferred way or shall I import views from django in shell or copy the function directly ? What is the best practice for this?

So your going to be much better off just writing Django tests for your views instead of trying to run them from the shell since it will be the same code, but you will be able to easily run the test multiple times.
So to create a test for a single view you would create a tests.py in your django app and write test for the view using django's test client. This test client is a dummy web browser that can be used to make http requests. A simple tests.py would look like this:
from django.tests import TestCase, Client
class MyViewsTestCase(TestCase):
def setUp(self):
self.client = Client() #This sets up the test client
def test_my_view(self):
# A simple test that the view returns a 200 status code
# In reality your test needs to check more than this depending on what your view is doing
response = self.client.get('the/view/url')
self.assertEqual(response.status_code, 200)
you would then run this with the commands python manage.py test or django-admin test from your terminal
Again you could do this from the shell, but it's going to be better in the long run to use the test framework
Django has some good docs on writing and running tests here: https://docs.djangoproject.com/en/2.0/topics/testing/overview/
and info on the test client along with some other testing tools here: https://docs.djangoproject.com/en/2.0/topics/testing/tools/

Related

Integrate redis with flask application using redis-py

I want to use redis-py directly to work with flask, instead of other wrapper lib (e.g. flask-redis, flask-and-redis) of flask plugins. How to initialize redis-client in factory function create_app() of flask application ? Or how to properly just initialize redis.StrictRedis (maybe not compatible with other flask plugins of redis ??) ? Because there're some operations related to token persistence in router of other modules using this redis.StrictRedis object.
Any advice, please ?
Interesting question, I think using the wrappers may make things easier but if you just want to use redis as a cache you can very easily import redis and just create/instantiate alongside the flask app.
import redis
from flask import Flask
# make redis
redis_cache = redis.StrictRedis()
# make flask app
app = Flask(__name__)
# business logic
#app.route('/<string:item>')
def index(item):
# if cache hit then get from redis
if redis_cache.exists(item):
value = redis_cache.get(item)
# cache miss
else:
value = 'Not in cache'
return value
This is the simplest way.
You can also follow this link to create the setup function and add the redis instantiation in the create_app function.

Populating db data for django's built-in testing tools

I'm using Django(1.8) + DRF + uwsgi + nginx and trying to unit-test API that I've made. To run tests I need to populate db (create users, for example) and use this data in all tests. So I've tried two ways:
Create directly in TestCase.setUp:
class ApiTests(TestCase):
def setUp(self):
Account.objects.create_user(username='username', password='password')
Or use fixtures:
class ApiTests(TestCase):
fixtures = ['dump.json']
Next I run my project through supervisor:
system("service supervisord startall")
After everything is ready I try to access my API in test to login, using:
login_data = {"username": "username", "password": "password"}
rslt = client.post(HOST_NAME + '/login/', data=login_data)
... but I can't authorize, because users somehow don't exist in the db!
As I've found in the docs to Django tests, TestCase doesn't write data into db, but store it in a transaction, that is rolled back after testing. And as I can see I can get this data only on test-side (using User.objects.all() that is showing that users are created), but not on my nginx-server-side (User.objects.all() on this side shows 0 items).
ATM I can see few options:
Somehow force TestCase to commit data into db.
Populate data in other methods (but which?).
Use different testing libs.
Could you please help?
You shouldn't use web server to test django views even though in reality views do need to be driven by web servers. In order to test request/response behavior, you should use django test client. Django doc has excellent example about that, quoting:
>>> from django.test import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'john', 'password': 'smith'})
>>> response.status_code
200
>>> response = c.get('/customer/details/')
>>> response.content
b'<!DOCTYPE html...'
On top of that, the doc explains in details how to do GET, POST request and how to test view's response, etc. Hope that makes sense.
Yes! You've already identified the problem. Django TestCase executes in its own little insulated environment. It will create test database prefixed with test_ at the beginning of each suite run, and will execute each test in a transaction, so even if you start the the test suite, pause it after initialization, set the django config that supervisord points to to the test database, continue test execution you will still not see any data.
The reason you will not see any data in the example above is because the test has its own transaction, then when you make a request to your webserver, the webserver will open up a different transaction and will not be able to see the uncommitted data from your test.
To test your views (in my experiences) django TestCases and test clients, usually get you 95% there. They are very fast because each test is executed in a transaction, they expose a test client, that mimics a request (url routing, middleware, view loading, template processing, etc).
Using TestCase should faithfully test all your logic and database interactions, but it still leaves a gap of if supervisor, nginx, uwsgi, django app are functioning correctly. When you have extensive coverage using TestCase simple integration/smoke tests should suffice to verify the services above can communicate correctly. Ie bring up the stack, hit a status page that will test -> Supervisor -> Nginx -> uwsgi -> django -> DB -> back out.
There are def a lot of options for purely functional tests, but why test at a flaky , timely to maintain level when django provides you the tools to verify your application in a reliable, quick, easy to use manner?
If you need a server to be available to a browser for a browser based test, django provides LiveServerTestCase
If you need to write extensive functional tests I have found great success in exposing fixutre creation methods as API methods. This way your tests would be executed AGAINST a stack, an arbitrary stack, in this case it will be against a test stack you bring up locally, but since the tests are separate they could be executed against a QA or staging or even prod stack.

why django SimpleTestCase create test database

as explained in this question and in django docs, when using SimpleTestCase in unit testing, django should not create test database (which takes too long).
Inside one of my applications which is called "search, I have some unit test inherited from SimpleTestCase. this is tests.py inside search application:
class TokenizerTestCase(SimpleTestCase):
def test_one(self):
self.assertItemsEqual(1, 1)
When I call python manage.py test search.tests.TokenizerTestCase it takes too long to build default database. does anybody know why it is creating database for test?
By default SimpleTestCase creates a test database. You can extend the class for your own functionality. If you do not want to create a database of your own in each and every setup setup your own test environment extending the classes.
Override the _pre_setup and _post_teardown methods. For more information read the source code for TransactionTestCase to see how it creates the test database structure.
Read the source code here

Using django-discover-runner without database

I'm trying to use django-discover-runner to test my app. It's basically a WebService frontend, so it doesn't include a database, and, apparently, django-discover-runner doesn't like that.
Looking in other questions, I've seen that with plain Django, I should inherit from DjangoTestSuiteRunner and set settings.TEST_RUNNER. It works fine. But django-discover-runner uses its own discover_runner.DiscoverRunner class, so I tried this:
from discover_runner import DiscoverRunner
class DBLessTestRunner(DiscoverRunner):
def setup_databases(self):
pass
def teardown_databases(self, *args):
pass
But it doesn't work. I get this error message:
ImproperlyConfigured: settings.DATABASES is improperly configured. Please supply the ENGINE value. Check settings documentation for more details.
Any idea how to get django-discover-runner working without a DataBase?
In Django 1.6 the standard Django TestCase inherits from TransactionTestCase which attempts to access the database.
To fix the problem in your test class inherit from SimpleTestCase rather then TestCase:
from django.test import SimpleTestCase
class TestViews(SimpleTestCase):
...
You should now be able to run your tests with out setting up the database.

Mocking Django Storages Model ImageField backend S3

I have a model with an ImageField that is backed by django-storages' S3Boto. I have a test the exercises the "upload image" view, but the fact that it is uploading the image to S3 is slowing down my test suite.
In the interest of speeding up my tests, what is the best practice for dealing with this issue? Should I mock out S3Boto? Perhaps there is a memory backed storage backend that works well for testing (automatic cleanup would be nice!)?
I just had this problem too. I got much faster tests by using dj-inmemorystorage.
The quick way of setting this up is by creating a test_settings.py in the same folder as your settings:
from settings import *
DEFAULT_FILE_STORAGE = 'inmemorystorage.InMemoryStorage'
...and calling ./manage.py test --settings=project.test_settings to run the tests.
My preferred way is to set up a custom test runner:
In project/test_runner.py:
from django.conf import settings
from django.test.runner import DiscoverRunner
class FastTestRunner(DiscoverRunner):
def setup_test_environment(self):
super(FastTestRunner, self).setup_test_environment()
# Don't write files
settings.DEFAULT_FILE_STORAGE = 'inmemorystorage.InMemoryStorage'
# Bonus: Use a faster password hasher. This REALLY helps.
settings.PASSWORD_HASHERS = (
'django.contrib.auth.hashers.MD5PasswordHasher',
)
Note: This also sets the PASSWORD_HASHER, because it significantly improves User creation time. This should NOT be set in production.
In project/settings.py:
TEST_RUNNER = 'project.test_runner.FastTestRunner'
The requirements:
pip install dj-inmemorystorage
UPDATE: changed from django-inmemorystorage to dj-inmemorystorage.
UPDATE 2: Removed django-discover-runner, as it's now the default test runner in django, and fixed the link to the PASSWORD_HASHER related blog post.
I also use S3Boto but for testing, I prefer having custom settings which include using the file system storage. You can have your custom settings declared in a file which you can then import and use in your test cases. Even so, you can mock the file storage so that the files are not actually written to disk.
Here's a sample test_settings.py
# myproject/myproject/test_settings.py
from django.test import override_settings
common_settings = override_settings(
DEFAULT_FILE_STORAGE='django.core.files.storage.FileSystemStorage',
PASSWORD_HASHERS=(
'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher',
),
)
Usage:
from django.test import TestCase
from myproject.test_settings import common_settings
#common_settings
class MyTestCase(TestCase):
"""Tests go here"""
On mocking the file system storage you can check out my answer here on SO.
Just ran into this as well so I thought I'd put my solution up. My solution uses Mock
import mock
from django.core.files.storage import FileSystemStorage
from django.test import TestCase
class ATestCase(TestCase):
def setUp(self):
# Stuff Happens
def tearDown(self):
# more Stuff
#mock.patch('storages.backends.s3boto.S3BotoStorage', FileSystemStorage)
def test_file_stuff(self):
self.assertMagicPonies(True)
Some gotchas - make sure you have a sane MEDIA_ROOT setup in the settings. as of django 1.4, you can't use the testing context manager to override MEDIA_ROOT, so you need a separate settings config for it (https://code.djangoproject.com/ticket/17787) This was fixed in 1.6. Also, make sure your upload_to works in normal filesystem, or you will get permission errors.
I would propose to use the standard Django Storage for testing, where you can a define custom path for storage and cleanup that path in your test suite once your done. Both the storage and the path can be set in the settings and overwritten for testing.