@@ -60,6 +60,87 @@ select using an argument to the ``django_db`` mark::
60
60
def test_spam():
61
61
pass # test relying on transactions
62
62
63
+
64
+ Async tests and database transactions
65
+ -------------------------------------
66
+
67
+ ``pytest-django `` supports async tests that use Django's async ORM APIs.
68
+ This requires the `pytest-asyncio <https://github.com/pytest-dev/pytest-asyncio >`_
69
+ plugin and marking your tests appropriately.
70
+
71
+ Requirements
72
+ """"""""""""
73
+
74
+ - Install ``pytest-asyncio ``.
75
+ - Mark async tests with both ``@pytest.mark.asyncio `` and
76
+ ``@pytest.mark.django_db `` (or request the ``db ``/``transactional_db `` fixtures).
77
+
78
+ Example (async ORM with transactional rollback per test)::
79
+
80
+ import pytest
81
+
82
+ @pytest.mark.asyncio
83
+ @pytest.mark.django_db
84
+ async def test_async_db_is_isolated():
85
+ assert await Item.objects.acount() == 0
86
+ await Item.objects.acreate(name="example")
87
+ assert await Item.objects.acount() == 1
88
+ # changes are rolled back after the test
89
+
90
+ .. _`async-db-behavior` :
91
+
92
+ Behavior of ``db `` in async tests
93
+ """""""""""""""""""""""""""""""""
94
+
95
+ Tests using ``db `` wrap each test in a transaction and roll that transaction back at the end
96
+ (like ``django.test.TestCase ``). In Django, transactions are bound to the database
97
+ connection, which is unique per thread. This means that all your database changes
98
+ must be made within the same thread to ensure they are rolled back before the next test.
99
+
100
+ Django Async ORM calls, as of writing, use the ``asgiref.sync.sync_to_async ``
101
+ decorator to run the ORM calls on a dedicated thread executor.
102
+
103
+ For async tests, pytest-django ensures the transaction
104
+ setup/teardown happens via ``asgiref.sync.sync_to_async ``, which means the transaction is started & run on the
105
+ same thread on which async orm calls inside your test, like ``aget() `` are made. This ensures your test code
106
+ can safely modify the database using the async calls, as all its queries will be rolled back after the test.
107
+
108
+ Tests using ``transactional_db `` flush the database between tests. This means that no matter in which thread
109
+ your test modifies the database, the changes will be removed after the test. This means you can avoid thinking
110
+ about sync/async database access if your test uses ``transactional_db ``, at the cost of slower tests:
111
+ A flush is generally slower than rolling back a transaction.
112
+
113
+ .. _`db-thread-safeguards` :
114
+ Safeguards against database access from different threads
115
+ """""""""""""""""""""""""""""""""""""""""""""""""""""""""
116
+ When using the database in a test with transaction rollback, you must ensure that
117
+ database access is only done from the same thread that the test is running on.
118
+
119
+ To avoid your fixtures/tests making changes outside the test thread, and as a result, the transaction, pytest-django
120
+ actively restricts where database connections may be opened:
121
+
122
+ - In async tests using ``db ``: database access is only allowed from the single
123
+ thread used by ``SyncToAsync ``. Using sync fixtures that touch the database in
124
+ an async test will raise::
125
+
126
+ RuntimeError: Database access is only allowed in an async context, modify your
127
+ test fixtures to be async or use the transactional_db fixture.
128
+
129
+ Fix by converting those fixtures to async (use ``pytest_asyncio.fixture ``) and
130
+ using Django's async ORM methods (e.g. ``.acreate() ``, ``.aget() ``, ``.acount() ``),
131
+ or by requesting ``transactional_db `` if you must keep sync fixtures.
132
+ See :ref: `async-db-behavior ` for more details.
133
+
134
+ - In sync tests: database access is only allowed from the main thread. Attempting to use the database connection
135
+ from a different thread will raise::
136
+
137
+ RuntimeError: Database access is only allowed in the main thread, modify your
138
+ test fixtures to be sync or use the transactional_db fixture.
139
+
140
+ Fix this by ensuring all database transactions run in the main thread (e.g., avoiding the use of async fixtures),
141
+ or use ``transactional_db `` to allow mixing.
142
+
143
+
63
144
.. _`multi-db` :
64
145
65
146
Tests requiring multiple databases
@@ -524,3 +605,4 @@ Put this in ``conftest.py``::
524
605
django_db_blocker.unblock()
525
606
yield
526
607
django_db_blocker.restore()
608
+
0 commit comments