Django testing: per test case urlconfs
Sometimes you might need to test a set of urlconf configurations in your django tests. One such scenario might be if your app supplies several swappable views that the user might choose based on their needs, or you want to provide the user a way to use their own view in place of an app-shipped one. For instance, shuup does this in several places by loading a view based on a setting, and embedding the loaded view in the urlconf.
The other day I needed to test multiple configurations of such dynamically-selected view with the test client - i.e. by making requests and inspecting the responses in the context of a certain usage workflow.
It turns out if you just use the
@override_settings decorator and
only change the setting that is used to load the view, django will
not reload the urlconf and your setting will have no effect. My
attemts at forcing django to reload the urlconf manually all failed
and the like didn’t appear to have any effect).
However, Django does reload the urls if you change the urlconf using
override_settings(ROOT_URLCONF=...). Initially this seemed very
inconvenient, as per the
the setting has to be set to string pointing to a module - and I didn’t
want to maintain a dedicated module just for the urlconf per each of
the test cases I wanted to have.
But lucklily django is equally as happy with
ROOT_URLCONF set to a
class (during testing):
# myapp/tests.py import pytest from django.http import HttpResponse from django.urls import include, path, reverse from django.test import override_settings from myapp.urls import urlpatterns as app_patterns def view_one(request): return HttpResponse("One") def view_two(request): return HttpResponse("Two") @pytest.mark.parametrize( "view, result", ((view_one, "One"), (view_two, "Two")) ) def test_dynamic_workflow(client, view, result): class TempUrls: # App urls overwrite_patterns = [ path("", view, name="index"), ] + app_patterns # Root urls urlpatterns = [ path("", include((overwrite_patterns, "myapp"))) ] with override_settings(ROOT_URLCONF=TempUrls): response = client.get(reverse("myapp:index")) assert response.content.decode("utf-8") == result
So we simply define a class (
TempUrls) within our test that hooks up
a different view for each test case (thanks to pytest’s
functionality, our test gets called twice - with a different
result arguments each time).
TempUrls class first defines the app urls by prepending our
customized urls to the app’s shipped ones (django uses the first
matched entry when resolving urls so duplicates are not a problem).
Then the class includes the app urls into the root urlconf with the
app’s namespace, so that all
reverse() calls down the path work like
in real world - with the namespace prefix.
And that’s it - each of your test cases will have a different urlconf!