When introducing some generic behaviour into my application, I like to separate that behaviour in some way from the remaining application code to prevent application specifics from leaking into the generic feature. This separation usually takes the form of a different module, package, Django app or in extreme cases even a separate distribution package.

Usually, I like to test the generic behaviour to ensure correctness and more importantly to enforce its internal interface. If Django views are involved, my code is bound to the project’s URL config though. Each URL reversal would reference project-specific URL naming like lead-tutorial-progress or an app name like tutorials:progress. Having this coupling encourages fellow developers to treat the behaviour as application-specific instead of generic.

Using the @override_settings decorator enables us to remove the coupling of tests to our specific project’s URL setup. This works because the app_name only namespaces when the URLconf is include()-ed, not when used as a direct ROOT_URLCONF.

tutorials/urls.pypython
from django.urls import path

from tutorials.views import TutorialProgressAPIView

app_name = "tutorials"
urlpatterns = [
    path(
        "progress/",
        TutorialProgressAPIView.as_view(),
        name="progress",
    ),
    ...
]
tutorials/tests.pypython
@override_settings(ROOT_URLCONF="tutorials.urls")
class TestTutorialProgressAPIView(AuthenticationMixin, TestCase):
    def test_store_progress(self):
        self.force_login()
        response = self.client.post(
            reverse("progress"),
            {"identifier": "example", "version": "initial"},
        )

        self.assertEqual(response.status_code, 201)

The preceding test POSTs to the tutorial progress endpoint without any application-specific URL references.

The app’s urls module can serve a dual purpose. First, it provides a clean project-independent URLconf, which results in the isolation shown above. Second, the very same file is the real wiring that is included in the actual project:

project/urls.pypython
urlpatterns = [
    path("tutorial/", include("tutorials.urls")),
]

As our tests run against this wiring, it is not only configured, but tested!