1
0
mirror of https://github.com/django/django.git synced 2024-12-23 01:25:58 +00:00
django/tests/test_utils/test_testcase.py
Simon Charette 3cf80d3fcf Fixed #31395 -- Made setUpTestData enforce in-memory data isolation.
Since it's introduction in Django 1.8 setUpTestData has been suffering
from a documented but confusing caveat due to its sharing of attributes
assigned during its execution with all test instances.

By keeping track of class attributes assigned during the setUpTestData
phase its possible to ensure only deep copies are provided to test
instances on attribute retreival and prevent manual setUp gymnastic to
work around the previous lack of in-memory data isolation.

Thanks Adam Johnson for the extensive review.
2020-05-15 20:22:52 +02:00

137 lines
4.8 KiB
Python

from functools import wraps
from django.db import IntegrityError, connections, transaction
from django.test import TestCase, ignore_warnings, skipUnlessDBFeature
from django.test.testcases import TestData
from django.utils.deprecation import RemovedInDjango41Warning
from .models import Car, Person, PossessedCar
class TestTestCase(TestCase):
@skipUnlessDBFeature('can_defer_constraint_checks')
@skipUnlessDBFeature('supports_foreign_keys')
def test_fixture_teardown_checks_constraints(self):
rollback_atomics = self._rollback_atomics
self._rollback_atomics = lambda connection: None # noop
try:
car = PossessedCar.objects.create(car_id=1, belongs_to_id=1)
with self.assertRaises(IntegrityError), transaction.atomic():
self._fixture_teardown()
car.delete()
finally:
self._rollback_atomics = rollback_atomics
def test_disallowed_database_connection(self):
message = (
"Database connections to 'other' are not allowed in this test. "
"Add 'other' to test_utils.test_testcase.TestTestCase.databases to "
"ensure proper test isolation and silence this failure."
)
with self.assertRaisesMessage(AssertionError, message):
connections['other'].connect()
with self.assertRaisesMessage(AssertionError, message):
connections['other'].temporary_connection()
def test_disallowed_database_queries(self):
message = (
"Database queries to 'other' are not allowed in this test. "
"Add 'other' to test_utils.test_testcase.TestTestCase.databases to "
"ensure proper test isolation and silence this failure."
)
with self.assertRaisesMessage(AssertionError, message):
Car.objects.using('other').get()
class NonDeepCopyAble:
def __deepcopy__(self, memo):
raise TypeError
def assert_no_queries(test):
@wraps(test)
def inner(self):
with self.assertNumQueries(0):
test(self)
return inner
class TestDataTests(TestCase):
# setUpTestData re-assignment are also wrapped in TestData.
jim_douglas = None
@classmethod
def setUpTestData(cls):
cls.jim_douglas = Person.objects.create(name='Jim Douglas')
cls.car = Car.objects.create(name='1963 Volkswagen Beetle')
cls.herbie = cls.jim_douglas.possessed_cars.create(
car=cls.car,
belongs_to=cls.jim_douglas,
)
cls.non_deepcopy_able = NonDeepCopyAble()
@assert_no_queries
def test_class_attribute_equality(self):
"""Class level test data is equal to instance level test data."""
self.assertEqual(self.jim_douglas, self.__class__.jim_douglas)
@assert_no_queries
def test_class_attribute_identity(self):
"""
Class level test data is not identical to instance level test data.
"""
self.assertIsNot(self.jim_douglas, self.__class__.jim_douglas)
@assert_no_queries
def test_identity_preservation(self):
"""Identity of test data is preserved between accesses."""
self.assertIs(self.jim_douglas, self.jim_douglas)
@assert_no_queries
def test_known_related_objects_identity_preservation(self):
"""Known related objects identity is preserved."""
self.assertIs(self.herbie.car, self.car)
self.assertIs(self.herbie.belongs_to, self.jim_douglas)
@ignore_warnings(category=RemovedInDjango41Warning)
def test_undeepcopyable(self):
self.assertIs(self.non_deepcopy_able, self.__class__.non_deepcopy_able)
def test_undeepcopyable_warning(self):
msg = (
"Assigning objects which don't support copy.deepcopy() during "
"setUpTestData() is deprecated. Either assign the "
"non_deepcopy_able attribute during setUpClass() or setUp(), or "
"add support for deepcopy() to "
"test_utils.test_testcase.TestDataTests.non_deepcopy_able."
)
with self.assertRaisesMessage(RemovedInDjango41Warning, msg):
self.non_deepcopy_able
def test_repr(self):
self.assertEqual(
repr(TestData('attr', 'value')),
"<TestData: name='attr', data='value'>",
)
class SetupTestDataIsolationTests(TestCase):
"""
In-memory data isolation is respected for model instances assigned to class
attributes during setUpTestData.
"""
@classmethod
def setUpTestData(cls):
cls.car = Car.objects.create(name='Volkswagen Beetle')
def test_book_name_deutsh(self):
self.assertEqual(self.car.name, 'Volkswagen Beetle')
self.car.name = 'VW sKäfer'
self.car.save()
def test_book_name_french(self):
self.assertEqual(self.car.name, 'Volkswagen Beetle')
self.car.name = 'Volkswagen Coccinelle'
self.car.save()