Skip to content

Support reset sequences #619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 41 additions & 11 deletions docs/helpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ on what marks are and for notes on using_ them.
.. _using: https://pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules


``pytest.mark.django_db(transaction=False)`` - request database access
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``pytest.mark.django_db`` - request database access
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. :py:function:: pytest.mark.django_db:
.. :py:function:: pytest.mark.django_db([transaction=False, reset_sequences=False]):

This is used to mark a test function as requiring the database. It
will ensure the database is set up correctly for the test. Each test
Expand All @@ -25,9 +25,9 @@ of the test. This behavior is the same as Django's standard
`django.test.TestCase`_ class.

In order for a test to have access to the database it must either
be marked using the ``django_db`` mark or request one of the ``db``
or ``transactional_db`` fixtures. Otherwise the test will fail
when trying to access the database.
be marked using the ``django_db`` mark or request one of the ``db``,
``transactional_db`` or ``django_db_reset_sequences`` fixtures. Otherwise the
test will fail when trying to access the database.

:type transaction: bool
:param transaction:
Expand All @@ -38,14 +38,23 @@ when trying to access the database.
uses. When ``transaction=True``, the behavior will be the same as
`django.test.TransactionTestCase`_


:type reset_sequences: bool
:param reset_sequences:
The ``reset_sequences`` argument will ask to reset auto increment sequence
values (e.g. primary keys) before running the test. Defaults to
``False``. Must be used together with ``transaction=True`` to have an
effect. Please be aware that not all databases support this feature.
For details see :py:attr:`django.test.TransactionTestCase.reset_sequences`.

.. note::

If you want access to the Django database *inside a fixture*
this marker will not help even if the function requesting your
fixture has this marker applied. To access the database in a
fixture, the fixture itself will have to request the ``db`` or
``transactional_db`` fixture. See below for a description of
them.
fixture, the fixture itself will have to request the ``db``,
``transactional_db`` or ``django_db_reset_sequences`` fixture. See below
for a description of them.

.. note:: Automatic usage with ``django.test.TestCase``.

Expand Down Expand Up @@ -217,8 +226,17 @@ mark to signal it needs the database.

This fixture can be used to request access to the database including
transaction support. This is only required for fixtures which need
database access themselves. A test function would normally use the
``pytest.mark.django_db`` mark to signal it needs the database.
database access themselves. A test function should normally use the
``pytest.mark.django_db`` mark with ``transaction=True``.

``django_db_reset_sequences``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This fixture provides the same transactional database access as
``transactional_db``, with additional support for reset of auto increment
sequences (if your database supports it). This is only required for
fixtures which need database access themselves. A test function should
normally use the ``pytest.mark.django_db`` mark with ``transaction=True`` and ``reset_sequences=True``.

``live_server``
~~~~~~~~~~~~~~~
Expand All @@ -229,6 +247,18 @@ or by requesting it's string value: ``unicode(live_server)``. You can
also directly concatenate a string to form a URL: ``live_server +
'/foo``.

.. note:: Combining database access fixtures.

When using multiple database fixtures together, only one of them is
used. Their order of precedence is as follows (the last one wins):

* ``db``
* ``transactional_db``
* ``django_db_reset_sequences``

In addition, using ``live_server`` will also trigger transactional
database access, if not specified.

``settings``
~~~~~~~~~~~~

Expand Down
56 changes: 43 additions & 13 deletions pytest_django/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

from .lazy_django import skip_if_no_django

__all__ = ['django_db_setup', 'db', 'transactional_db', 'admin_user',
'django_user_model', 'django_username_field',
__all__ = ['django_db_setup', 'db', 'transactional_db', 'django_db_reset_sequences',
'admin_user', 'django_user_model', 'django_username_field',
'client', 'admin_client', 'rf', 'settings', 'live_server',
'_live_server_helper', 'django_assert_num_queries']

Expand Down Expand Up @@ -107,7 +107,8 @@ def teardown_database():
request.addfinalizer(teardown_database)


def _django_db_fixture_helper(transactional, request, django_db_blocker):
def _django_db_fixture_helper(request, django_db_blocker,
transactional=False, reset_sequences=False):
if is_django_unittest(request):
return

Expand All @@ -120,6 +121,11 @@ def _django_db_fixture_helper(transactional, request, django_db_blocker):

if transactional:
from django.test import TransactionTestCase as django_case

if reset_sequences:
class ResetSequenceTestCase(django_case):
reset_sequences = True
django_case = ResetSequenceTestCase
else:
from django.test import TestCase as django_case

Expand All @@ -139,7 +145,7 @@ def _disable_native_migrations():

@pytest.fixture(scope='function')
def db(request, django_db_setup, django_db_blocker):
"""Require a django test database
"""Require a django test database.

This database will be setup with the default fixtures and will have
the transaction management disabled. At the end of the test the outer
Expand All @@ -148,30 +154,54 @@ def db(request, django_db_setup, django_db_blocker):
This is more limited than the ``transactional_db`` resource but
faster.

If both this and ``transactional_db`` are requested then the
database setup will behave as only ``transactional_db`` was
requested.
If multiple database fixtures are requested, they take precedence
over each other in the following order (the last one wins): ``db``,
``transactional_db``, ``django_db_reset_sequences``.
"""
if 'django_db_reset_sequences' in request.funcargnames:
request.getfixturevalue('django_db_reset_sequences')
if 'transactional_db' in request.funcargnames \
or 'live_server' in request.funcargnames:
request.getfixturevalue('transactional_db')
else:
_django_db_fixture_helper(False, request, django_db_blocker)
_django_db_fixture_helper(request, django_db_blocker, transactional=False)


@pytest.fixture(scope='function')
def transactional_db(request, django_db_setup, django_db_blocker):
"""Require a django test database with transaction support
"""Require a django test database with transaction support.

This will re-initialise the django database for each test and is
thus slower than the normal ``db`` fixture.

If you want to use the database with transactions you must request
this resource. If both this and ``db`` are requested then the
database setup will behave as only ``transactional_db`` was
requested.
this resource.

If multiple database fixtures are requested, they take precedence
over each other in the following order (the last one wins): ``db``,
``transactional_db``, ``django_db_reset_sequences``.
"""
if 'django_db_reset_sequences' in request.funcargnames:
request.getfuncargvalue('django_db_reset_sequences')
_django_db_fixture_helper(request, django_db_blocker,
transactional=True)


@pytest.fixture(scope='function')
def django_db_reset_sequences(request, django_db_setup, django_db_blocker):
"""Require a transactional test database with sequence reset support.

This behaves like the ``transactional_db`` fixture, with the addition
of enforcing a reset of all auto increment sequences. If the enquiring
test relies on such values (e.g. ids as primary keys), you should
request this resource to ensure they are consistent across tests.

If multiple database fixtures are requested, they take precedence
over each other in the following order (the last one wins): ``db``,
``transactional_db``, ``django_db_reset_sequences``.
"""
_django_db_fixture_helper(True, request, django_db_blocker)
_django_db_fixture_helper(request, django_db_blocker,
transactional=True, reset_sequences=True)


@pytest.fixture()
Expand Down
22 changes: 14 additions & 8 deletions pytest_django/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from .fixtures import django_user_model # noqa
from .fixtures import django_username_field # noqa
from .fixtures import live_server # noqa
from .fixtures import django_db_reset_sequences # noqa
from .fixtures import rf # noqa
from .fixtures import settings # noqa
from .fixtures import transactional_db # noqa
Expand Down Expand Up @@ -383,13 +384,15 @@ def django_db_blocker():
def _django_db_marker(request):
"""Implement the django_db marker, internal to pytest-django.

This will dynamically request the ``db`` or ``transactional_db``
fixtures as required by the django_db marker.
This will dynamically request the ``db``, ``transactional_db`` or
``django_db_reset_sequences`` fixtures as required by the django_db marker.
"""
marker = request.node.get_closest_marker('django_db')
if marker:
transaction = validate_django_db(marker)
if transaction:
transaction, reset_sequences = validate_django_db(marker)
if reset_sequences:
request.getfixturevalue('django_db_reset_sequences')
elif transaction:
request.getfixturevalue('transactional_db')
else:
request.getfixturevalue('db')
Expand Down Expand Up @@ -671,11 +674,14 @@ def restore(self):
def validate_django_db(marker):
"""Validate the django_db marker.

It checks the signature and creates the `transaction` attribute on
the marker which will have the correct value.
It checks the signature and creates the ``transaction`` and
``reset_sequences`` attributes on the marker which will have the
correct values.

A sequence reset is only allowed when combined with a transaction.
"""
def apifun(transaction=False):
return transaction
def apifun(transaction=False, reset_sequences=False):
return transaction, reset_sequences
return apifun(*marker.args, **marker.kwargs)


Expand Down
Loading