pytest-django icon indicating copy to clipboard operation
pytest-django copied to clipboard

ChannelsLiveServerTestCase equivalent for pytest

Open nrbnlulu opened this issue 2 years ago • 11 comments

In pytest-django there is a builtin fixture live_server though it seems like this server (that is actually based on LiveServerTestCase) can't handle web-sockets or at least won't interact with my asgi.py module.

How can one mimic that fixture in order to use ChannelsLiveServerTestCase instead? Or anything else that will run a test-database and will be able to serve an ASGI application?

My goal eventually is to have as close to production environment as possible, for testing and being able to test interaction between different Consumers.

nrbnlulu avatar Sep 21 '22 04:09 nrbnlulu

I tried some stuff, but couldn't get it to work. Maybe someone can take a look: https://github.com/django/daphne/discussions/451

medihack avatar Nov 09 '22 09:11 medihack

Got it finally working with Channels / Daphne 4.0.0:

from functools import partial
from channels.routing import get_default_application
from daphne.testing import DaphneProcess
from django.contrib.staticfiles.handlers import ASGIStaticFilesHandler
from django.core.exceptions import ImproperlyConfigured
from django.db import connections
from django.test.utils import modify_settings


def make_application(*, static_wrapper):
    # Module-level function for pickle-ability
    application = get_default_application()
    if static_wrapper is not None:
        application = static_wrapper(application)
    return application


class ChannelsLiveServer:
    host = "localhost"
    ProtocolServerProcess = DaphneProcess
    static_wrapper = ASGIStaticFilesHandler
    serve_static = True

    def __init__(self) -> None:
        for connection in connections.all():
            if connection.vendor == "sqlite" and connection.is_in_memory_db():
                raise ImproperlyConfigured(
                    "ChannelsLiveServer can not be used with in memory databases"
                )

        self._live_server_modified_settings = modify_settings(ALLOWED_HOSTS={"append": self.host})
        self._live_server_modified_settings.enable()

        get_application = partial(
            make_application,
            static_wrapper=self.static_wrapper if self.serve_static else None,
        )

        self._server_process = self.ProtocolServerProcess(self.host, get_application)
        self._server_process.start()
        self._server_process.ready.wait()
        self._port = self._server_process.port.value

    def stop(self) -> None:
        self._server_process.terminate()
        self._server_process.join()
        self._live_server_modified_settings.disable()

    @property
    def url(self) -> str:
        return f"http://{self.host}:{self._port}"


@pytest.fixture
def channels_liver_server(request):
    server = ChannelsLiveServer()
    request.addfinalizer(server.stop)
    return server

medihack avatar Nov 09 '22 13:11 medihack

what's request? def channels_liver_server(request):

nrbnlulu avatar Nov 09 '22 13:11 nrbnlulu

what's request? def channels_liver_server(request):

A built in fixture of pytest, see https://docs.pytest.org/en/7.1.x/reference/reference.html#request

medihack avatar Nov 09 '22 15:11 medihack

@medihack Is this already implemented as fixture in Pytest? I cannot find it there.

flaiming avatar Jun 05 '23 08:06 flaiming

@flaiming Nope, I don't think so, and not sure if the pytest-django team is interested (maybe because of the additional channels dependency). That said, I have been using it as a custom fixture until now without a problem.

medihack avatar Jun 05 '23 09:06 medihack

Any info on how to get the code above working with selenium browser logins? After adding the cookie, everything seems to work fine but as soon, as I browser.refresh(), my authentication cookie in sessionid is gone, despite there being a valid Session object in the database.

I also had to manually create my defined test database from test.py in Postgres for it to work, as it somehow didn't seem to create it automatically with asgiref.

@pytest.fixture
def browser(request):
    """ Selenium webdriver fixture """
    options = webdriver.ChromeOptions()
    browser_ = webdriver.Chrome(options=options)
    yield browser_
    browser_.quit()


@pytest.fixture
def authenticated_browser_staff(browser, client, channels_live_server, user_staff):
    """ Return a browser instance with logged-in user session. """
    browser.get(channels_live_server.url)
    client.get(channels_live_server.url)
    client.force_login(user_staff)
    browser.add_cookie({
        'name': settings.SESSION_COOKIE_NAME,
        'value': client.cookies[settings.SESSION_COOKIE_NAME].value,
        'expires': None,
        'secure': False,
        'path': '/'
    })
    browser.refresh()
    assert any(c for c in browser.get_cookies() if c['name'] == settings.SESSION_COOKIE_NAME)
    return browser

alfonsrv avatar Sep 03 '23 18:09 alfonsrv

see this SO https://stackoverflow.com/questions/73140518/channelsliveservertestcase-equivalent-for-pytest/75046701#75046701

nrbnlulu avatar Sep 03 '23 18:09 nrbnlulu

Doesn't work using both channels_live_server_thread and channels_live_server_proc from your POC https://github.com/nrbnlulu/channels_pytest_liveserver/blob/master/tests/test_liveserver.py

alfonsrv avatar Sep 03 '23 19:09 alfonsrv

Make sure you have database access... Also what is the error (if any)?

nrbnlulu avatar Sep 04 '23 04:09 nrbnlulu

I realized while my user_staff is passed to authenticated_browser_staff correctly (with a pk etc) and I can authenticate them using client, the browser + daphne server cannot create / access the test database.

Can it have something to do with make_application and it somehow re-initializing the Django app or Daphne running in a different thread, thus not keeping the test database alive?

alfonsrv avatar Sep 04 '23 20:09 alfonsrv