Skip to content

Commit 1bf9be6

Browse files
author
Lode Rosseel
committed
Reduce code duplication: reuse test case class creation logic for sync & async fixtures
1 parent cca2dff commit 1bf9be6

File tree

1 file changed

+59
-78
lines changed

1 file changed

+59
-78
lines changed

pytest_django/fixtures.py

Lines changed: 59 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,47 @@ def django_db_setup(
201201
)
202202

203203

204+
def _build_pytest_django_test_case(
205+
test_case_class,
206+
*,
207+
reset_sequences: bool,
208+
serialized_rollback: bool,
209+
databases,
210+
available_apps,
211+
skip_django_testcase_class_setup: bool,
212+
):
213+
# Build a custom TestCase subclass with configured attributes and optional
214+
# overrides to skip Django's TestCase class-level setup/teardown.
215+
import django.test # local import to avoid hard dependency at import time
216+
217+
_reset_sequences = reset_sequences
218+
_serialized_rollback = serialized_rollback
219+
_databases = databases
220+
_available_apps = available_apps
221+
222+
class PytestDjangoTestCase(test_case_class): # type: ignore[misc,valid-type]
223+
reset_sequences = _reset_sequences
224+
serialized_rollback = _serialized_rollback
225+
if _databases is not None:
226+
databases = _databases
227+
if _available_apps is not None:
228+
available_apps = _available_apps
229+
230+
if skip_django_testcase_class_setup:
231+
232+
@classmethod
233+
def setUpClass(cls) -> None: # type: ignore[override]
234+
# Skip django.test.TestCase.setUpClass, call its super instead
235+
super(django.test.TestCase, cls).setUpClass()
236+
237+
@classmethod
238+
def tearDownClass(cls) -> None: # type: ignore[override]
239+
# Skip django.test.TestCase.tearDownClass, call its super instead
240+
super(django.test.TestCase, cls).tearDownClass()
241+
242+
return PytestDjangoTestCase
243+
244+
204245
@pytest.fixture
205246
def _sync_django_db_helper(
206247
request: pytest.FixtureRequest,
@@ -248,41 +289,14 @@ def _sync_django_db_helper(
248289
else:
249290
test_case_class = django.test.TestCase
250291

251-
_reset_sequences = reset_sequences
252-
_serialized_rollback = serialized_rollback
253-
_databases = databases
254-
_available_apps = available_apps
255-
256-
class PytestDjangoTestCase(test_case_class): # type: ignore[misc,valid-type]
257-
reset_sequences = _reset_sequences
258-
serialized_rollback = _serialized_rollback
259-
if _databases is not None:
260-
databases = _databases
261-
if _available_apps is not None:
262-
available_apps = _available_apps
263-
264-
# For non-transactional tests, skip executing `django.test.TestCase`'s
265-
# `setUpClass`/`tearDownClass`, only execute the super class ones.
266-
#
267-
# `TestCase`'s class setup manages the `setUpTestData`/class-level
268-
# transaction functionality. We don't use it; instead we (will) offer
269-
# our own alternatives. So it only adds overhead, and does some things
270-
# which conflict with our (planned) functionality, particularly, it
271-
# closes all database connections in `tearDownClass` which inhibits
272-
# wrapping tests in higher-scoped transactions.
273-
#
274-
# It's possible a new version of Django will add some unrelated
275-
# functionality to these methods, in which case skipping them completely
276-
# would not be desirable. Let's cross that bridge when we get there...
277-
if not transactional:
278-
279-
@classmethod
280-
def setUpClass(cls) -> None:
281-
super(django.test.TestCase, cls).setUpClass()
282-
283-
@classmethod
284-
def tearDownClass(cls) -> None:
285-
super(django.test.TestCase, cls).tearDownClass()
292+
PytestDjangoTestCase = _build_pytest_django_test_case(
293+
test_case_class,
294+
reset_sequences=reset_sequences,
295+
serialized_rollback=serialized_rollback,
296+
databases=databases,
297+
available_apps=available_apps,
298+
skip_django_testcase_class_setup=(not transactional),
299+
)
286300

287301
PytestDjangoTestCase.setUpClass()
288302

@@ -319,9 +333,6 @@ async def _async_django_db_helper(
319333
) -> AsyncGenerator[None, None]:
320334
# same as _sync_django_db_helper, except for running the transaction start and rollback wrapped in a
321335
# `sync_to_async` call
322-
if is_django_unittest(request):
323-
yield
324-
return
325336
transactional, reset_sequences, databases, serialized_rollback, available_apps = (
326337
_get_django_db_settings(request)
327338
)
@@ -330,46 +341,16 @@ async def _async_django_db_helper(
330341
import django.db
331342
import django.test
332343

333-
if transactional:
334-
test_case_class = django.test.TransactionTestCase
335-
else:
336-
test_case_class = django.test.TestCase
337-
338-
_reset_sequences = reset_sequences
339-
_serialized_rollback = serialized_rollback
340-
_databases = databases
341-
_available_apps = available_apps
342-
343-
class PytestDjangoTestCase(test_case_class): # type: ignore[misc,valid-type]
344-
reset_sequences = _reset_sequences
345-
serialized_rollback = _serialized_rollback
346-
if _databases is not None:
347-
databases = _databases
348-
if _available_apps is not None:
349-
available_apps = _available_apps
350-
351-
# For non-transactional tests, skip executing `django.test.TestCase`'s
352-
# `setUpClass`/`tearDownClass`, only execute the super class ones.
353-
#
354-
# `TestCase`'s class setup manages the `setUpTestData`/class-level
355-
# transaction functionality. We don't use it; instead we (will) offer
356-
# our own alternatives. So it only adds overhead, and does some things
357-
# which conflict with our (planned) functionality, particularly, it
358-
# closes all database connections in `tearDownClass` which inhibits
359-
# wrapping tests in higher-scoped transactions.
360-
#
361-
# It's possible a new version of Django will add some unrelated
362-
# functionality to these methods, in which case skipping them completely
363-
# would not be desirable. Let's cross that bridge when we get there...
364-
if not transactional:
365-
366-
@classmethod
367-
def setUpClass(cls) -> None:
368-
super(django.test.TestCase, cls).setUpClass()
369-
370-
@classmethod
371-
def tearDownClass(cls) -> None:
372-
super(django.test.TestCase, cls).tearDownClass()
344+
test_case_class = django.test.TestCase
345+
346+
PytestDjangoTestCase = _build_pytest_django_test_case(
347+
test_case_class,
348+
reset_sequences=reset_sequences,
349+
serialized_rollback=serialized_rollback,
350+
databases=databases,
351+
available_apps=available_apps,
352+
skip_django_testcase_class_setup=True,
353+
)
373354

374355
from asgiref.sync import sync_to_async
375356

0 commit comments

Comments
 (0)