Mocking Django Storages Model ImageField backend S3 - django

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.

Related

django: Use different configuration for test database?

Can I specific a different configuration for the test database? Or alternatives simply use a different user in production?(how to manage that as settings file needs to be updated as well?) The testing requirements for postgresql require in my opinion too many privs like create DB and in my case I also need to create an extension upon db creation which means superuser privileges.
This is very messy, but a valid workaround nonetheless. We use a snippet of code like this in settings.py to run tests using a local sqlite3 database.
import sys
if 'test' in sys.argv or 'test_coverage' in sys.argv:
DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
DATABASES['default']['NAME'] = os.path.join(BASE_DIR, 'db.sqlite3')

Custom faker provider for usage with factory boy and pytest

I am attempting to add some custom faker provider to use with factory_boy and pytest.
I put the provider in
faker_providers/foo.py/Provider.
In my factories.py file, I have to import foo.py and then register by running:
factory.Faker.add_provider(foo.Provider)
I am thinking of using pytest_sessionstart(session) to auto-register all the custom provider under faker_providers. Is there a way to do that?
Any suggestions for other ways to organize and register custom providers would also be appreciated.
Instead of instantiating a Faker to import from conftest, as of 2022, you can do the following inside your conftest file:
from factory import Faker
Faker.add_provider(CustomProvider)
And now you can just import from factory import Faker wherever you go. This works because the Faker class has a class attribute of type dictionary that saves your new providers.
The reason I put it in conftest is because this is where code is run initially for all pytest. You can also put this in a pytest plugin setup method as well, but I found this the easiest.
The other way of doing this that I implemented was to have a utils folder with all my custom providers. Then, in a providers.py, I would add all my providers:
from factory import Faker
from .custom_provider import CustomProvider
for provider in (CustomProvider,):
Faker.add_provider(provider)
Then in conftest.py, I would simply import this file:
import path.to.provider # noqa
This way, I don't clutter my conftest too much.
It seems like a design choice and only you know the best answer to it.
However, I would recommend instantiating faker = Faker() once for all tests after that adding all providers in a configuration file. And import faker from that place to everywhere it's needed.
It seems like conftest.py is a good choice for that.

what is the best way to test modules in views?

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/

Where to initialize MongoDB connection in Django projects?

I wonder where I should initialize my MongoDB connection in my Django projects.
Currently I am initializing the client and db before my view functions in views.py:
import pymongo
from django.conf import settings
client = pymongo.MongoClient(settings.MONGO_URI)
db = client.get_default_database()
def some_view(request):
pass
However I also need to use MongoDB in my models.py in conjunction with Django signals. What do you suggest?
Maybe settings.py? Or even root __init__.py? Then you can import client and db everywhere you need it.
I've decided to use project/mongodb.py (same folder as settings.py)
import pymongo
from django.conf import settings
client = pymongo.MongoClient(settings.MONGO_URI)
mongodb = client.get_default_database()
I am using two different settings files for local and production. Therefore, this approach makes it possible to use environment dependent settings, while enabling me to access mongodb variable from anywhere in the project.

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.