How can I override settings for code ran in urls.py while unit testing django
my django app has a env var DEMO
which, among other thing, dictate what endpoints are declared in my urls.py
file.
I want to unit tests these endpoints, I've tried django.test.override_settings
but I've found that urls.py
is ran only once and not once per unit test.
My code look like this:
# settings.py
DEMO = os.environ.get("DEMO", "false") == "true"
# urls.py
print(f"urls.py: DEMO = {settings.DEMO}")
if settings.DEMO:
urlpatterns += [
path('my_demo_endpoint/',MyDemoAPIView.as_view(),name="my-demo-view")
]
# test.test_my_demo_endpoint.py
class MyDemoEndpointTestCase(TestCase):
@override_settings(DEMO=True)
def test_endpoint_is_reachable_with_demo_equals_true(self):
print(f"test_endpoint_is_reachable_with_demo_equals_true: DEMO = {settings.DEMO}")
response = self.client.get("/my_demo_endpoint/")
# this fails with 404
self.assertEqual(response.status_code, 200)
@override_settings(DEMO=False)
def test_endpoint_is_not_reachable_with_demo_equals_false(self):
print(f"test_endpoint_is_not_reachable_with_demo_equals_false: DEMO = {settings.DEMO}")
response = self.client.get("/my_demo_endpoint/")
self.assertEqual(response.status_code, 404)
when running this I get:
urls.py: DEMO = False
test_endpoint_is_reachable_with_demo_equals_true: DEMO = True
<test fails with 404>
test_endpoint_is_not_reachable_with_demo_equals_false: DEMO = False
<test succeed>
urls.py is ran only once before every test, however I want to test different behavious of urls.py depending on settings
Using a different settings file for testing is not a solution because different tests requires different settings. Directly calling my view in the unit test means that the urls.py code stays uncovered and its behaviour untested so this is also not what I want.
How can I override settings for code ran in urls.py?
Thank you for your time.
As you already wrote above, urls.py
is loaded once during the app's startup and does not reload between tests unless you explicitly reload it. To test different urls.py
behaviors based on settings, you can dynamically import and reload the urls.py
module within each test.
Use Python's importlib.reload
to re-import and evaluate urls.py
after changing the settings.
import importlib
from django.conf import settings
from django.test import override_settings, TestCase
from django.urls import resolve, reverse
class MyDemoEndpointTestCase(TestCase):
def reload_urls(self):
import myproject.urls # Adjust to your `urls.py` location
importlib.reload(myproject.urls)
@override_settings(DEMO=True)
def test_endpoint_is_reachable_with_demo_equals_true(self):
self.reload_urls()
self.assertTrue(settings.DEMO) # Verify setting was applied
response = self.client.get("/my_demo_endpoint/")
self.assertEqual(response.status_code, 200)
@override_settings(DEMO=False)
def test_endpoint_is_not_reachable_with_demo_equals_false(self):
self.reload_urls()
self.assertFalse(settings.DEMO) # Verify setting was applied
response = self.client.get("/my_demo_endpoint/")
self.assertEqual(response.status_code, 404)
I wanted to bring your attention to this interesting warning from django docs:
The settings file contains some settings that are only consulted during initialization of Django internals. If you change them with override_settings, the setting is changed if you access it via the django.conf.settings module, however, Django’s internals access it differently. Effectively, using override_settings() or modify_settings() with these settings is probably not going to do what you expect it to do.
This warning is about half way on this page in django docs. Urls file is one of those places that it's only consulted once during initialization.
If you always want settings.DEMO = True
for all your tests, it might be worth creating a separate settings file just for tests. I see you need to test various values - please try the below solution
I use Pytest, so my test would be really simple. I hope it helps. First step instead of using @override_settings
decorator I would make a test fixutre. I would add value of that variable to the test fixture and pass it to my test. Maybe you can do something similar in your test setup?
from django.conf import settings
@pytest.fixture()
def test_settings_demo_true(settings):
settings.DEMO = True
@pytest.mark.django_db
def test_endpoint_is_reachable_with_demo_equals_true(client, test_settings_demo_true):
print(f"test_endpoint_is_reachable_with_demo_equals_true: DEMO = {settings.DEMO}")
response = client.get("/my_demo_endpoint/")
# this should now pass
assert response.status_code == 200
@pytest.fixture()
def test_settings_demo_false(settings):
settings.DEMO = False
@pytest.mark.django_db
def test_endpoint_is_reachable_with_demo_equals_false(client, test_settings_demo_false):
print(f"test_endpoint_is_not_reachable_with_demo_equals_true: DEMO = {settings.DEMO}")
response = client.get("/my_demo_endpoint/")
# this should now pass
assert response.status_code == 404
Alternatively you can try this:
from django.conf import settings
class MyDemoEndpointTestCase(TestCase):
def test_endpoint_is_reachable_with_demo_equals_true(self):
settings.DEMO = True
print(f"test_endpoint_is_reachable_with_demo_equals_true: DEMO = {settings.DEMO}")
response = self.client.get("/my_demo_endpoint/")
# this fails with 404
self.assertEqual(response.status_code, 200)
def test_endpoint_is_not_reachable_with_demo_equals_false(self):
settings.DEMO = False
print(f"test_endpoint_is_not_reachable_with_demo_equals_false: DEMO = {settings.DEMO}")
response = self.client.get("/my_demo_endpoint/")
self.assertEqual(response.status_code, 404)