pythondjangotesting

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.


Solution

  • Vitaliy Desyatka's answer nearly works other than the fact that Django caches the URL resolver. This can be seen in Django's code, specifically the function django.urls.resolvers._get_cached_resolver is doing this. You can modify Vitaliy Desyatka's answer to add clearing of the cache to it as well like so:

    import importlib
    from django.urls import clear_url_caches
    
    
    class MyDemoEndpointTestCase(TestCase):
        def reload_urls(self):
            import myproject.urls  # Adjust to your `urls.py` location
            importlib.reload(myproject.urls)
            clear_url_caches()
    

    The above will get your tests to pass with the caveats that:

    An alternative would be for you to have a dedicated URLconf for when DEMO = True and instead of overriding DEMO during the tests just override ROOT_URLCONF (or override both). So your settings can look something like:

    DEMO = os.environ.get("DEMO", "false") == "true"
    
    
    ROOT_URLCONF = 'myproject.urls' # Your default URLConf when DEMO is False
    if DEMO:
        ROOT_URLCONF = 'myproject.demo_urls'
    

    And then alongside your urls.py file you can have a demo_urls.py file like so:

    from .urls import urlpatterns as base_urlpatterns
    
    urlpatterns = base_urlpatterns + [
        # Your demo urls go here
    ]
    

    Now when you want to test your demo URLs you can override the URLConf as:

    @override_settings(ROOT_URLCONF='myproject.demo_urls')
    def test_foo(self):
        pass