Django SECURE_SSL_REDIRECT breaks unit tests that

2020-07-10 08:47发布

问题:

I am working on a project that already has hundreds of unit tests, many of which use either the built in django Client, or the django rest framework APIClient for making requests and testing responses.

Having recently implemented the necessaries to make SSL work locally, and setting the SECURE_SSL_REDIRECT to True (trying to make our dockerised dev and test environments as close to production as possible), I have come a cropper to find that so many unit tests fail, due to the (API)Clients requesting, by default, always using http, not https.

Many (most) requests look like this:

response = self.client.get(some_url)

I am aware that I could use:

response = self.client.get(some_url, secure=True)

But this does mean changing a lot of unit tests. The same is true for using follow=True, but had the added disadvantage that this could produce some other undesired behaviour.

I cannot see a way of setting the use of secure requests as a default behaviour in the Django Client. I could make my own SecureClient (and SecureAPIClient), but I would then have to make sure that I make a new base TestCase (possibly multiple) to inherit from, and change this everywhere for all the tests - still a lot of work.

It is possible of course to monkey patch the Client, but I am reluctant to to do this as, again, it could have undesired effects that are hard to debug later.

TLDR; Is there a simple (ideally supported) way, to make all unit test requests via the django test's Client, to use SSL by default?

回答1:

  • Option 1. Disable the SECURE_SSL_REDIRECT when needed:

    from django.test import override_settings
    
    class FooTest(TestCase):
    
        def setUp(self):
            settings_manager = override_settings(SECURE_SSL_REDIRECT=False)
            settings_manager.enable()
            self.addCleanup(settings_manager.disable)
    
  • Option 2: Wrap get and post methods of the APIClient:

    class ApiTestCase(TestCase):
        def setUp(self):
            self.client = APIClient()
    
        def get(self, *args, **kwargs):
            return self.client.get(secure=True, *args, **kwargs)
    
        def post(self, *args, **kwargs):
            return self.client.post(secure=True, *args, **kwargs)
    
  • Option 3: just maintain a separate settings file for the test environment with the SECURE_SSL_REDIRECT not being set.

I've tried all three options and recommend going with the (3). Here is why:

  1. django.test.client is just an imitation of a real client. It's there to unit test your views. It doesn't produce real WSGI requests, so test environment won't match production environment anyway.

  2. SECURE_SSL_REDIRECT is a SecurityMiddleware setting. Don't unit test third-party code.

  3. Making sure that your code always uses SSL is a good goal. However, this is an integration testing problem. The right tool for this job is Selenium + LiveServerTestCase.



回答2:

If you're using pytest and pytest-django, you can also make a fixture that modifies the test client:

import functools
import pytest

@pytest.fixture
def client(client):
    client.get = functools.partial(client.get, secure=True)
    client.post = functools.partial(client.post, secure=True)
    return client