Lets say module a code:
from django.conf import settings
print settings.BASE_URL # prints http://example.com
In tests.py I want to mock the BASE_URL to http://localhost
I have tried the following:
with mock.patch('django.conf.settings.BASE_URL', 'http://localhost'):
pass
with mock.patch('a.settings.BASE_URL', 'http://localhost'):
pass
from a import settings
with mock.patch.object(settings, 'BASE_URL', 'http://localhost'):
pass
import a
with mock.patch.object(a.settings, 'BASE_URL', 'http://localhost'):
pass
None of the above worked.
Try to use context manager settings() built-in django.
with self.settings(BASE_URL='http://localhost'):
# perform your test
https://docs.djangoproject.com/en/dev/topics/testing/tools/#django.test.SimpleTestCase.settings
You can also use the following decorator on your individual test functions or test class as a whole. For example:
from django.test import override_settings
#override_settings(BASE_URL='http://localhost')
def test_case()
...
Related
So I am trying to mock a function and it is not being mocked in the module where it is being called, but it when called directly in the test function. I am unsure what I'm doing wrong. I use patch all the time without any problems.
So in the test I have below
from unittest.mock import patch
from django.test import TestCase
from web.utils.registration import test_render_mock
class TestRenderMock(TestCase):
#patch("django.shortcuts.render")
def test_render_mock(self, render_mock):
request = self.make_request()
# Calls render in same way in function below
test_render_mock(request)
from django.shortcuts import render
r = render(request, 'c')
print(type(r))
which is calling the function in the file web/utils/registration.py
from django.shortcuts import render
def test_render_mock(request):
r = render(request, 'registration/signup_link_error.html')
print(type(r))
And it isn't mocking the render call in the function test_render_mock but it is in the test function test_render_mock(). The console output is below.
<class 'django.http.response.HttpResponse'>
<class 'unittest.mock.MagicMock'>
I have no idea what I'm doing wrong. Any help would be appreciated.
Using python version 3.8.2 and Django 3.0.5
UPDATE
I used to use the third party library mock, but using the built in one now from tne unittest library.
now for third party library you need to define the module path where there imported like so
from unittest.mock import patch
from django.test import TestCase
from web.utils.registration import test_render_mock
MODULE_TESTING: str ='...'
class TestRenderMock(TestCase):
#patch(f"{MODULE_TESTING}.render")
def test_render_mock(self, render_mock):
request = self.make_request()
# Calls render in same way in function below
test_render_mock(request)
from django.shortcuts import render
r = render(request, 'c')
print(type(r))
I am using pytest in my django project. I used pytest.config previously to get conftest.py values into the setUp function but it is deprecated since version 4.1. In the docs they suggest to use request fixture to get configs but I couldn't find a proper way get configs inside to the TestCase class using request fixture. Following code sample is the working code which uses the pytest.config. Any way to get configs without getting PytestDeprecationWarning will be helpful.
import pytest
from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
#override_settings(USE_SUBDOMAIN=False, PRODUCTION_DOMAIN='example.com')
class TestSample(TestCase):
fixtures = ['test_data']
def setUp(self):
url_base = '{scheme}://{domain}/sample'.format(
scheme=pytest.config.option.url_scheme,
domain=settings.PRODUCTION_DOMAIN,
)
Is there a defined best practice for defining custom settings that come with a django app.
What I have at the moment is a separate file called app_settings.py that looks like this:
from django.conf import settings
# MY_SETTING_1 is required and will brake the application if not defined
try:
MY_SETTING_1 = str(getattr(settings, 'MY_SETTING_1'))
except AttributeError:
raise ImproperlyConfigured ('MY_SETTING_1 not defined in settings.py.')
# MY_SETTING_2 is not required and has a default value
MY_SETTING_2 = getattr(settings, 'MY_SETTING_2', ['default_value'])
Then I am importing this in my views.py, and using it like this:
from my_app import app_settings
print (app_settings.MY_SETTING_1)
print (app_settings.MY_SETTING_2)
This works ok, but I am not quite happy with the design. I am assuming that I can somehow use the class defined in apps.py, but I am not sure how.
Is there a best (or better) practice for this?
You could try https://pypi.org/project/typed_app_settings/.
Example:
# my_app/app_settings.py
from typed_app_settings import UndefinedValue, typed_app_settings_dict
#typed_app_settings_dict("MY_APP")
class Settings:
MY_SETTING_1: str = UndefinedValue()
MY_SETTING_2: str = "default_value"
settings = Settings()
Then in the view you would just use it like this.
from my_app.app_settings import settings
print(app_settings.MY_SETTING_1)
print(app_settings.MY_SETTING_2)
Am working on a django reusable package that am planning to use with multiple project. I've used pytest to build test suite, I've used parametrized helped in pytest to run a single test with multiple configuration.
Yet, I would like to run all my tests using different settings combinations
available_backends = [
'django_profile.auth_backends.drf.RestFramework',
'django_profile.auth_backends.kong.Kong',
]
def pytest_generate_tests(metafunc):
# if 'stringinput' in metafunc.fixturenames:
if 'auth_backend' in metafunc.fixturenames:
metafunc.parametrize(
'auth_backend',
available_backends
)
#pytest.fixture(params=['auth_backend', ])
def auth_backend(request, settings):
settings.DJANGO_PROFILE_AUTH_BACKEND = request.auth_backend
return settings
I experimented with the above approach, but this also means I have to add auth_backend to each test case, I don't believe this is ideal. Any one can recommend a way for me to run all my tests using different setting combinations?
Regards
please did you try:
to use code in conftest.py with scope="session"
use available_backends list directly in params= instead of pytest_generate_tests
https://docs.pytest.org/en/latest/fixture.html#parametrizing-fixtures
if not, could you try using this:
#pytest.fixture(scope='session', params=[
'django_profile.auth_backends.drf.RestFramework',
'django_profile.auth_backends.kong.Kong',
])
def auth_backend(request, settings):
settings.DJANGO_PROFILE_AUTH_BACKEND = request.param
yield settings
you can override settings for each test.
django documentation:
Overriding settings
for example:
from django.test import TestCase
class LoginTestCase(TestCase):
def test_login(self):
# First check for the default behavior
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/accounts/login/?next=/sekrit/')
# Then override the LOGIN_URL setting
with self.settings(LOGIN_URL='/other/login/'):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
or use for decorator
from django.test import TestCase, override_settings
class LoginTestCase(TestCase):
#override_settings(LOGIN_URL='/other/login/')
def test_login(self):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
Eventually, I found a way to pass different settings to all tests without having to specify the fixture in each test. Below is a working example
# conftest.py
#pytest.yield_fixture(params=available_backends)
def settings(request, settings):
if request.param not in available_backends:
raise ValueError('auth_backend {} not implemented.'.format(request.param))
wrapper = SettingsWrapper()
settings.DJANGO_PROFILE_AUTH_BACKEND = request.param
yield wrapper
wrapper.finalize()
# setup.cfg
usefixtures = settings
Django allows overwriting settings, when running tests through SimpleTestCase.settings() (https://docs.djangoproject.com/en/1.8/topics/testing/tools/#django.test.SimpleTestCase.settings). That works fine when I try to overwrite one of Django's settings.
The app I want to run tests for carries its own settings in an app-specific settings.py with the following structure to allow overwriting the app-specific settings in the project wide settings.py:
from django.conf import settings
APP_SETTING1 = getattr(settings, 'APP_SETTING1', 'foo')
The following dummy code in a test shows the problem:
from django.test import TestCase
from django.conf import settings as django_settings
from my_app import settings
class MyTestCase(TestCase):
def test_something(self):
with self.settings(APP_SETTING1='bar'):
print(django_settings.APP_SETTING1) # bar
print(settings.APP_SETTING1) # foo
Why isn't that working and how can I make it work?
you can use django-zero-settings, it lets you define your default settings, and will update them with user settings provided, it also works fine in tests settings overrides, alongside that it auto import string, lets you define removed settings and pre-check user settings too.
as in your case, you can define app settings like this:
from zero_settings import ZeroSettings
app_settings = ZeroSettings(
key="APP",
defaults={
"SETTING1": "foo"
},
)
then in your tests you can use it like:
from django.test import TestCase
from django.conf import settings as django_settings
from my_app import app_settings
class MyTestCase(TestCase):
def test_something(self):
with self.settings(APP={"SETTING1": "bar"}):
print(django_settings.APP["SETTING1"]) # bar
print(app_settings.SETTING1) # bar
self.assertEqual(django_settings.APP["SETTING1"], app_settings.SETTING1)
because that ZeroSettings uses cache and will cache user settings on the first attempt to get key, you may need to set it off before getting keys, to make sure you get the latest updates:
app_settings = ZeroSettings(
key="APP",
defaults={
"SETTING1": "foo"
},
use_cache=False,
)
or clear the cache manually:
class MyTestCase(TestCase):
def test_something(self):
print(app_settings.SETTING1) # foo
with self.settings(APP={"SETTING1": "bar"}):
app_settings._clear_cache()
print(django_settings.APP["SETTING1"]) # bar
print(app_settings.SETTING1) # bar
self.assertEqual(django_settings.APP["SETTING1"], app_settings.SETTING1)
Ideally you should mock settings using unittest library incorporated into Django. Alternatively, you can do this, but it is better to patch those:
https://docs.djangoproject.com/en/2.1/topics/settings/#custom-default-settings
Actually, there are good practices for handling multiple settings files. The basic rules are:
1) Do not import from settings.py file directly:
# BAD - avoid this
from app.settings import SOME_ENV
Because if in the end, someone will try to use custom settings some parts of the old settings will be applied somewhere.
# GOOD
from django.conf import settings
print(settings.SOME_ENV)
In this case, you could be sure that actual settings were used.
2) Split up settings files for different environments, usually, it accomplished via creating several files:
settings/base.py
DEBUG = False
AWS_STORAGE_PREFIX = 'local'
...
settings/dev.py
from settings.base import *
DEBUG = True
AWS_STORAGE_PREFIX = 'dev'
DATABASE = {# your settings for dev env}
...
It is also quite usual to create a separated file for test runner because it gives you more power of controlling your test env and so on:
python manage.py test --settings=app.settings.testrunner
Test instance has bound settings' context manager, so you can override any variables that you need for testing:
class MyTestCase(TestCase):
def test_something(self):
# Old settings
with self.settings(SOME_ENV_VAR='overridden'):
# Overridden settings
pass
...
You can do it directly in apps using a certain setting
settings.py:
TEST_ENVIRONMENT_SETTING = True # Test setting for my app
apps.py:
from django.conf import settings
class MyAppConfig(AppConfig):
def ready(self):
if getattr(settings, 'TEST_ENVIRONMENT_SETTING', False):
# Do stuff or not do stuff
Or you can directly set this setting in your TestCase using
setattr(settings, 'TEST_ENVIRONMENT_SETTING', True)
I'm struggling with the same problem, and I think the only solution is to change approach and use only django.conf.settings directly.
The reason is that if you use a custom settings, your custom settings is taken too early from settings, before override_settings or TestCase.settings is called.