jazzband / django-hosts

Dynamic and static host resolving for Django. Maps hostnames to URLconfs.
http://django-hosts.rtfd.org
Other
982 stars 106 forks source link

Testing related issues; documentation & helpers needed to django-hosts ? #88

Open elnygren opened 6 years ago

elnygren commented 6 years ago

Some things I stumbled upon that others might find useful

Issue 1: how to make sure the right urlconf is used based on host when running tests

Some helpers to survive tests:

class DjangoHostsTestCase(TestCase):
    """Extends django.test.TestCase by setting a default SERVER_NAME to test client.

    This is most likely necessary when testing code using django_hosts.
    """
    @property
    def client(self):
        return self._client

    @client.setter
    def client(self, value):
        value.defaults.setdefault('SERVER_NAME', 'the-new-hostname')
        self._client = value

def reverse(*args, **kwargs):
    """Improved reverse that uses a specific django_hosts host."""
    kwargs.setdefault('host', 'the-default-host')
    return django_hosts.resolvers.reverse(*args, **kwargs)

NOTE: you could also just

response = self.client.get('/', SERVER_NAME='the-new-hostname')

Issue 2: django_hosts messes up with my existing tests that use reverse (namespace not registed)

Add following to TestCase:

from django.urls.base import set_urlconf
def tearDown(self):
        # revert urlconf as django_hosts & self.client SERVER_NAME mess with it
        set_urlconf(settings.ROOT_URLCONF)
coler-j commented 5 years ago

Can't you just do something like:

@override_settings(DEFAULT_HOST='the-new-hostname')
class MyTest(TestCase):
    ....
XJOJIX commented 3 years ago

it doesn't work with host_url in templates.

browniebroke commented 5 months ago

I recently joined a project where the tests are using the host header:

from django.test import TestCase
from django_hosts import reverse

class UserViewTestCase(TestCase):
    def test_user_list(self):
        # The url looks like http://api.example.com/v1/users/
        url = reverse("list-user", host="api")
        # Repeat the host in the requests (instead of using SERVER_NAME)
        response = self.client.get(url, headers={"host": "api.example.com"})
        assert response.status_code == 200

Which I didn't find particularly DRY. I just found out about the SERVER_NAME, but it feels like it's also repeating ourselves.

The Django TestCase class actually has a client_class attribute to control which client class is used, so I created a custom client class, which one could use in a custom test case class:

from urllib.parse import urlparse

from django.test import Client

class HostsClient(Client):

    def generic(self, method, path, *args, **extra):
        """Used by all methods."""
        if path.startswith("http"):
            # Populate the host header from the URL host, to play nicely with django-hosts
            _scheme, netloc, *_others = urlparse(path)
            extra.setdefault("headers", {})
            extra["headers"]["host"] = netloc
        return super().generic(method, path, *args, **extra)

Which turns the test into:

class UserViewTestCase(TestCase):
    client_class = HostsClient

    def test_user_list(self):
        url = reverse("list-user", host="api")
        # No need to set the host header anymore
        response = self.client.get(url)
        assert response.status_code == 200

Feels a bit simpler than passing the header/server name for each call to client.get(...) / client.post(...) / ... Now if you combine this with @coler-j suggestion, the test is even simpler:

@override_settings(DEFAULT_HOST='api')
class UserViewTestCase(TestCase):
    client_class = HostsClient

    def test_user_list(self):
        url = reverse("list-user")
        response = self.client.get(url)
        assert response.status_code == 200

Which looks almost like vanilla Django. Would you be interested in including this test client to django-hosts and update the documentation accordingly?

joonhyungshin commented 3 months ago

I like @browniebroke 's solution. It looks pretty neat. Would be great to have it in the official package.

Perhaps we could include a new TestCase too?

from django.test import TestCase as DjangoTestCase

class TestCase(DjangoTestCase):
    client_class = HostsClient
browniebroke commented 3 months ago

I've gotten around to add this https://github.com/jazzband/django-hosts/pull/169 - open to feedback