pytest-django
pytest-django copied to clipboard
Wire up cls.setUpTestData for class level fixtures
Reimplement the Django TestCase's setUpTestData. Since we are already calling the setUpClass machinery from TestCase, it's a simple step to arrange for PytestDjangoTestCase to call the real test class's setUpTestData classmethod.
This allows us to defer to the existing Django lifecycle hook machinery, and also use the Django 3.2 implementation for TestData which uses a descriptor to handle re-loading model instances between tests. (It does this by memoizing the pre-test instances, so this avoids us having to add a DB transaction or refresh_from_db() to reset the testData.)
Partially fixes #514
WIP: So far I've just verified that unit tests run on Py 3.7, I want to get some directional feedback before spending time polishing this and getting everything green.
I looked for solutions to the more general problem of "run session-scoped Django fixtures", but it turns out to be a bit challenging. The core problem is thusly:
To write the most "pytestian" fixtures, I'd want to write a session-scoped fixture, something like
@pytest.fixture(scope="session")
def item_1():
item_1 = Item.objects.create()
yield item_1
However, there are a couple problems:
- This is session-scoped fixture, so it can't depend on the existing
dbfunction-scoped fixture. We'd need to have a new fixture to unblock the DB and do the testcase setup.
@blueyed suggested something like this:
@pytest.fixture(scope="session")
def django_db_setup(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
with transaction.atomic(): # XXX: could/should use `TestCase_enter_atomics` for multiple dbs
load_time_consuming_db_fixture()
yield
Which has the follow-on issue:
- this unblocks the DB for the whole session; so if you have mixed "true UTs" (no DB) and integration tests, you can't rely on pytest-django's DB locking any more. (Maybe this isn't a blocking issue? Django itself doesn't attempt to do the DB blocking, so there's an argument that it could be worth giving up the DB blocker to gain session-scoped Django fixtures.
Also, 3. This does not let us use the state-of-the-art TestData caching from Django 3.2. If we wrap everything in a transaction, even if we get this to play nicely with a TestCase (non-TransactionTestCase), we still need some way to reload the model instances to their pre-test state. (Else we're back to Django pre-3.2, where the test setUp needs to call obj.reload_from_db() on each model instance. This last objection is not insurmountable, but note that we need to do some sort of registration of model instances, and arrange for the underlying objects to get refreshed somehow -- it's not clear how we'd actually figure out in a generic way what model instances were created.
Maybe I'm overcomplicating this though -- I'm not familiar with the pytest machinery, so it's very likely there's an approach I didn't think of.