Fixed #35660 -- Made serialized_rollback and fixture data available in TransactionTestCase.setUpClass().

This commit is contained in:
Jacob Walls 2024-09-04 09:33:44 -04:00 committed by Sarah Boyce
parent 8eca3e9bce
commit a060a22ee2
8 changed files with 91 additions and 16 deletions

View File

@ -208,6 +208,7 @@ class SimpleTestCase(unittest.TestCase):
async_client_class = AsyncClient
_overridden_settings = None
_modified_settings = None
_pre_setup_ran_eagerly = False
databases = set()
_disallowed_database_msg = (
@ -360,7 +361,10 @@ class SimpleTestCase(unittest.TestCase):
if not skipped:
try:
self._pre_setup()
if self.__class__._pre_setup_ran_eagerly:
self.__class__._pre_setup_ran_eagerly = False
else:
self._pre_setup()
except Exception:
if debug:
raise
@ -1090,6 +1094,7 @@ class TransactionTestCase(SimpleTestCase):
# Subclasses can enable only a subset of apps for faster tests
available_apps = None
_available_apps_calls_balanced = 0
# Subclasses can define fixtures which will be automatically installed.
fixtures = None
@ -1107,6 +1112,20 @@ class TransactionTestCase(SimpleTestCase):
# This can be slow; this flag allows enabling on a per-case basis.
serialized_rollback = False
@classmethod
def setUpClass(cls):
super().setUpClass()
if not issubclass(cls, TestCase):
cls._pre_setup()
cls._pre_setup_ran_eagerly = True
@classmethod
def tearDownClass(cls):
super().tearDownClass()
if not issubclass(cls, TestCase) and cls._available_apps_calls_balanced > 0:
apps.unset_available_apps()
cls._available_apps_calls_balanced -= 1
@classmethod
def _pre_setup(cls):
"""
@ -1119,6 +1138,7 @@ class TransactionTestCase(SimpleTestCase):
super()._pre_setup()
if cls.available_apps is not None:
apps.set_available_apps(cls.available_apps)
cls._available_apps_calls_balanced += 1
setting_changed.send(
sender=settings._wrapped.__class__,
setting="INSTALLED_APPS",
@ -1216,8 +1236,9 @@ class TransactionTestCase(SimpleTestCase):
for conn in connections.all(initialized_only=True):
conn.close()
finally:
if self.available_apps is not None:
if self.__class__.available_apps is not None:
apps.unset_available_apps()
self.__class__._available_apps_calls_balanced -= 1
setting_changed.send(
sender=settings._wrapped.__class__,
setting="INSTALLED_APPS",

View File

@ -260,6 +260,11 @@ Tests
failures easier to read and enables :option:`test --pdb` to directly enter
into the failing test method.
* Data loaded from :attr:`~django.test.TransactionTestCase.fixtures` and from
migrations enabled with :ref:`serialized_rollback=True
<test-case-serialized-rollback>` are now available during
``TransactionTestCase.setUpClass()``.
URLs
~~~~

View File

@ -280,6 +280,11 @@ To prevent serialized data from being loaded twice, setting
:data:`~django.db.models.signals.post_migrate` signal when flushing the test
database.
.. versionchanged:: 5.2
For :class:`TransactionTestCase`, serialized migration data is made
available during ``setUpClass()``.
Other test conditions
---------------------

View File

@ -1262,25 +1262,35 @@ subclass::
Here's specifically what will happen:
* At the start of each test, before ``setUp()`` is run, Django will flush the
database, returning the database to the state it was in directly after
:djadmin:`migrate` was called.
* During ``setUpClass()``, all the named fixtures are installed. In this
example, Django will install any JSON fixture named ``mammals``, followed by
any fixture named ``birds``. See the :ref:`fixtures-explanation` topic for
more details on defining and installing fixtures.
* Then, all the named fixtures are installed. In this example, Django will
install any JSON fixture named ``mammals``, followed by any fixture named
``birds``. See the :ref:`fixtures-explanation` topic for more details on
defining and installing fixtures.
For most unit tests using :class:`TestCase`, Django doesn't need to do
anything else, because transactions are used to clean the database after each
test for performance reasons. But for :class:`TransactionTestCase`, the
following actions will take place:
For performance reasons, :class:`TestCase` loads fixtures once for the entire
test class, before :meth:`~TestCase.setUpTestData`, instead of before each
test, and it uses transactions to clean the database before each test. In any case,
you can be certain that the outcome of a test will not be affected by another
test or by the order of test execution.
* At the end of each test Django will flush the database, returning the
database to the state it was in directly after :djadmin:`migrate` was
called.
* For each subsequent test, the fixtures will be reloaded before ``setUp()``
is run.
In any case, you can be certain that the outcome of a test will not be
affected by another test or by the order of test execution.
By default, fixtures are only loaded into the ``default`` database. If you are
using multiple databases and set :attr:`TransactionTestCase.databases`,
fixtures will be loaded into all specified databases.
.. versionchanged:: 5.2
For :class:`TransactionTestCase`, fixtures were made available during
``setUpClass()``.
URLconf configuration
---------------------

View File

@ -1,3 +1,4 @@
from django.core.management import call_command
from django.test import TestCase, TransactionTestCase
from .models import Book
@ -19,6 +20,26 @@ class MigrationDataPersistenceTestCase(TransactionTestCase):
)
class MigrationDataPersistenceClassSetup(TransactionTestCase):
"""
Data loaded in migrations is available during class setup if
TransactionTestCase.serialized_rollback = True.
"""
available_apps = ["migration_test_data_persistence"]
serialized_rollback = True
@classmethod
def setUpClass(cls):
# Simulate another TransactionTestCase having just torn down.
call_command("flush", verbosity=0, interactive=False)
super().setUpClass()
cls.book = Book.objects.first()
def test_data_available_in_class_setup(self):
self.assertIsInstance(self.book, Book)
class MigrationDataNormalPersistenceTestCase(TestCase):
"""
Data loaded in migrations is available on TestCase

View File

@ -4,7 +4,7 @@ from django.db import connections
from django.test import TestCase, TransactionTestCase, override_settings
from django.test.testcases import DatabaseOperationForbidden
from .models import Car
from .models import Car, Person
class TestSerializedRollbackInhibitsPostMigrate(TransactionTestCase):
@ -68,3 +68,16 @@ class DisallowedDatabaseQueriesTests(TransactionTestCase):
)
with self.assertRaisesMessage(DatabaseOperationForbidden, message):
Car.objects.using("other").get()
class FixtureAvailableInSetUpClassTest(TransactionTestCase):
available_apps = ["test_utils"]
fixtures = ["person.json"]
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.elvis = Person.objects.get(name="Elvis Presley")
def test_fixture_loaded_during_class_setup(self):
self.assertIsInstance(self.elvis, Person)

View File

@ -1214,7 +1214,7 @@ class XMLEqualTests(SimpleTestCase):
class SkippingExtraTests(TestCase):
fixtures = ["should_not_be_loaded.json"]
fixtures = ["person.json"]
# HACK: This depends on internals of our TestCase subclasses
def __call__(self, result=None):