Django Pytest Test URL Based on Settings

4.4k Views Asked by At

I have an endpoint /docs in django that I only want to be visible when DEBUG = True in settings - otherwise, it should throw a 404. My setup looks like this

urls.py

urlpatterns = ...

if settings.DEBUG:
    urlpatterns += [
            url(r'^docs/$', SwaggerSchemaView.as_view(), name='api_docs'),
    ]

When doing testing, though, django doesn't automatically reload urls.py, which means simply overriding DEBUG to True or False doesn't work.

My tests look something like this

@override_settings(DEBUG=True)
@override_settings(ROOT_URLCONF='config.urls')
class APIDocsTestWithDebug(APITestCase):
    # check for 200s
    ...

@override_settings(DEBUG=False)
@override_settings(ROOT_URLCONF='config.urls')
class APIDocsTestWithoutDebug(APITestCase):
    # check for 404s
    ...

Now here's the weird part: When I run the tests individually using pytest path/to/test.py::APIDocsTestWithDebug and pytest path/to/test.py::APIDocsTestWithoutDebug, both tests pass. However, if I run the test file as a whole (pytest path/to/test.py), APIDocsTestWithDebug always fails. The fact that they work individually but not together tells me that the url override is working, but when the tests are in tandem, there is some bug that messes things up. I was wondering if anybody had come across a similar issue and either has an entirely different solution or can give me some tips as to what I'm doing wrong.

3

There are 3 best solutions below

4
Sergei Nikiforov On

I struggled with the same issue. The thing is that Django loads your urlpatterns once while initializing - and overriding the settings with the decorator doesn't change what was initially loaded.

Here's what worked for me - try reloading your urls module (based on this) and clearing url caches with clear_url_caches() before the failing test cases:

import sys

from importlib import reload, import_module

from django.conf import settings
from django.core.urlresolvers import clear_url_caches  # Or -> from django.urls import clear_url_caches

def reload_urlconf(urlconf=None):
    clear_url_caches()
    if urlconf is None:
        urlconf = settings.ROOT_URLCONF
    if urlconf in sys.modules:
        reload(sys.modules[urlconf])
    else:
        import_module(urlconf)

PS: You might also want to restore the urlpatterns later - just run reload_urlconf within other settings.

0
vdboor On

You can use @pytest.mark.urls: https://pytest-django.readthedocs.io/en/latest/helpers.html#pytest.mark.urls

@pytest.mark.urls('myapp.test_urls')
def test_something(client):
    assert 'Success!' in client.get('/some_url_defined_in_test_urls/').content

You could even define the URLs within the same file:

def some_view(request):
    return HttpResponse(b"Success!")


urlpatterns = [
    path("some-url/", some_view)
]


@pytest.mark.urls(__name__)
def test_something(client):
    assert b'Success!' in client.get('/some-url/').content
0
Qsebas On

Based on Sergei Nikiforov's Answer I have implemented a Pypi package with a context manager for loading/reloading urls

Github Page: https://github.com/karpyncho/reload-urls

Install:

pip install karpyncho_reload_urls

then you simply inherits your TestCase suit class from TestCaseReloadableURL

and you can simply use:

from karpyncho.reload_urls import TestCaseReloadableURL
 
class TestMyClass(TestCaseReloadableURL)
    def my_test(self):
        with self.reload_urls(DEBUG=True):
            # put your checks here

that will reload your settings only in the context block

you are free to propose improvements...