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.
from django.urls import path
from tutorials.views import TutorialProgressAPIView
app_name = "tutorials"
urlpatterns = [
path(
"progress/",
TutorialProgressAPIView.as_view(),
name="progress",
),
...
]@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:
urlpatterns = [
path("tutorial/", include("tutorials.urls")),
]As our tests run against this wiring, it is not only configured, but tested!