Why does Django's LiveServerTestCase inherit from TransactionTestCase rather than TestCase, and what does it give a browser test?
answer
- a server in another thread
- separate connection, committed data
- live_server_url on a free port
- StaticLiveServerTestCase for assets
basics
~20 sLiveServerTestCase 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 linesfrom 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
Know that LiveServerTestCase runs a real server for browser tests and that its URL is self.live_server_url.
Explain why it extends TransactionTestCase: separate thread, separate connection, data must be committed.
Handle the in-memory SQLite shared-connection caveat, static-file serving, and the cost of flush per test in browser suites.
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