skip to content

Why does Django's LiveServerTestCase inherit from TransactionTestCase rather than TestCase, and what does it give a browser test?

level: middleimportance: should knowfreq 34%

answer

  1. a server in another thread
  2. separate connection, committed data
  3. live_server_url on a free port
  4. StaticLiveServerTestCase for assets

basics

~20 s

LiveServerTestCase starts a real Django server in a background thread for browser tests. That thread uses its own database connection, which cannot see a TestCase's uncommitted data, so the class commits and flushes like TransactionTestCase.

solid answer

~40 s

`django.test.LiveServerTestCase` starts a threaded HTTP server running your project in `setUpClass()` and stops it at the end, so a browser driver such as Selenium can load real pages at `self.live_server_url`. It listens on `localhost` and binds to port `0`, meaning the operating system picks a free port, and it appends the host to `ALLOWED_HOSTS` for the class. It extends `TransactionTestCase` because the server thread has its own database connection: rows a test creates inside `TestCase`'s uncommitted transaction would be invisible to it. With `TransactionTestCase` the data is committed and visible, and tables are flushed after each test. The exception is an in-memory SQLite database, where the two threads share one connection and must not query at the same time. To serve static files through the finders without `collectstatic`, use `StaticLiveServerTestCase` from `django.contrib.staticfiles.testing`.

code

python · 22 lines
python
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium.webdriver import Firefox
from selenium.webdriver.common.by import By

from tickets.models import Event


class SeatPickerBrowserTests(StaticLiveServerTestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()  # starts the live server thread
        cls.browser = Firefox()

    @classmethod
    def tearDownClass(cls):
        cls.browser.quit()
        super().tearDownClass()

    def test_event_page_lists_event(self):
        Event.objects.create(name="Jazz night", capacity=120)  # committed, visible to the server
        self.browser.get(f"{self.live_server_url}/events/")
        self.assertIn("Jazz night", self.browser.find_element(By.TAG_NAME, "body").text)

go deeper

for a junior

Know that LiveServerTestCase runs a real server for browser tests and that its URL is self.live_server_url.

for a middle

Explain why it extends TransactionTestCase: separate thread, separate connection, data must be committed.

for a senior

Handle the in-memory SQLite shared-connection caveat, static-file serving, and the cost of flush per test in browser suites.

for a principal

Keep browser tests few and focused on flows that need them, and budget their runtime separately from the fast suite.

## What it is for Django's test `Client` calls views in-process: no sockets, no browser, no JavaScript. For **functional tests** that drive a real browser, you need a real server. `django.test.LiveServerTestCase` provides one: - in `setUpClass()` it starts a `LiveServerThread` that serves the project over HTTP; - `self.live_server_url` (for example `http://localhost:54321`) points at it; - when the class finishes, a class cleanup stops the thread. ## Why `TransactionTestCase` The server runs in **another thread** and, for ordinary databases, uses **its own database connection**. Transactions are per connection: | Test base | Where test data lives | Can the server thread see it? | |---|---|---| | `TestCase` | an uncommitted transaction on the test thread's connection | no | | `TransactionTestCase` | committed rows | yes | If `LiveServerTestCase` extended `TestCase`, the event a test creates would not exist from the server's point of view, and the browser would get 404 pages. So it extends `TransactionTestCase`: the test commits its data, the server reads it, and Django **flushes** the tables after each test. Everything that applies to `TransactionTestCase` applies here: no `setUpTestData()`, migration data removed by flushes unless `serialized_rollback = True`, and slower resets. ## The in-memory SQLite exception When the test database is an **in-memory SQLite** database, a second connection would open a different, empty database. Django therefore **shares the test thread's connection** with the server thread. Django's docs warn that the two threads must not run queries at the same time, or tests fail at random. In practice, after clicking a link or submitting a form, wait until the browser has received the next page before touching the database from the test. ## Configuration - **Host and port**: class attributes `host = "localhost"` and `port = 0`; port 0 asks the operating system for a free port, so parallel runs do not collide. Override `port` only if something outside needs a fixed one. - **`ALLOWED_HOSTS`**: the class appends its host for the duration, so requests are not rejected. - **Static files**: the base class serves files from `STATIC_ROOT` at `STATIC_URL`, which needs `collectstatic`. `StaticLiveServerTestCase` from `django.contrib.staticfiles.testing` serves them through the staticfiles finders instead, like the development server does. - **Databases**: the `databases` attribute works as on `TransactionTestCase`. ## Writing one 1. Subclass `StaticLiveServerTestCase` (or `LiveServerTestCase`). 2. Start the browser driver in `setUpClass()` after calling `super().setUpClass()`, and quit it in `tearDownClass()` before calling `super()`. 3. Create data in the test or in `setUp()`; it is committed and visible to the server. 4. Navigate to `f"{self.live_server_url}/events/"` and assert on the page. ## Keeping them in proportion Live-server tests are the slowest kind in a Django suite: a browser, a server thread and a flush per test. Keep them for the flows that need a browser (JavaScript seat pickers, multi-step checkout) and test the rest with the in-process client in `TestCase`. ## Common failures and their causes - **The browser shows a 404 for an object the test just created**: the data is not visible to the server, usually because a subclass or mixin wrapped the test in a transaction that never commits. - **Random database errors with in-memory SQLite**: the test thread queried while the server thread was handling a request on the shared connection; wait for the page before querying. - **Unstyled pages**: static files are not being served; switch to `StaticLiveServerTestCase` or run `collectstatic` first. - **Data from a data migration missing**: a previous transactional class flushed it; set `serialized_rollback = True` or create the rows in `setUp()`.

  • What is the difference between Django's LiveServerTestCase and StaticLiveServerTestCase?
    `LiveServerTestCase` serves static files from `STATIC_ROOT`, so they must have been collected first. `StaticLiveServerTestCase`, in `django.contrib.staticfiles.testing`, swaps in the staticfiles handler that finds assets through the configured finders, as the development server does, so no `collectstatic` step is needed. Everything else, including the transactional behaviour, is the same.

saying these in an interview costs you the question

  • LiveServerTestCase wraps each test in a rolled-back transaction
  • The live server always runs on port 8081
  • LiveServerTestCase serves static files from app directories without collectstatic
  • setUpTestData works in LiveServerTestCase
  • The live server thread can see uncommitted test data on PostgreSQL