Pytest-django: setup examples

Created on 12 Apr 2013  路  8Comments  路  Source: pytest-dev/pytest-django

i've tried to find examples on how to use new fixture mark w/ pytest_django.
something like:

import pytest
from django.contrib.sites.models import Site
from django.contrib.auth.models import User

@pytest.fixture(scope="session", autouse=True)
@pytest.mark.django_db
def project_setup(*args, **kwargs):
    user = User.objects.create(username="session user #1")
    assert User.objects.count(), 1
    site, created = Site.objects.get_or_create(id=1)
    site.domain = "testserver"
    site.save()

to do some db modifications after test db is ready.
this fails w/ error "Failed: Database access not allowed, use the "django_db" mark to enable"

could somebody provide an example on howto setup additional stuff after test db is ready?

documentation sprint-idea

Most helpful comment

This is quite old, but I had a similar problem and found a work-around that worked for me, so in case someone else stumbles across this:

@pytest.fixture(autouse=True, scope='session')
def post_db_setup(_django_db_setup, _django_cursor_wrapper):
    with _django_cursor_wrapper:
        call_command('loaddata', *settings.STATIC_FIXTURES, interactive=False)

I realize fixtures are not the preferred way to do things, but if you _really_ need to fill your DB with some values before any of the tests run... and only once, then that works quite well. It makes sure _django_db_setup has been done and you get _django_cursor_wrapper to write to it (I suspect any model creation would work in that context, but haven't tried it).

All 8 comments

This clearly needs to be documented better with examples, I will try to improve the docs.

In the meantime, I hope this can help you:
1) Fixtures which deals with database modifications must use the default scope of 'function' - the database will be reset after each test.

2) You can request the db fixture for your own fixture. Practically this means:

@pytest.fixture(autouse=True)
def project_setup(db):  # the db argument will make sure the database access is requested for this fixture
    User.objects.create(username="foo")

You might have something specific in mind, but I just want to give you a warning: I would generally advice against automatically requesting database access for _all_ your tests. You will not be able to turn this of later! You will simply not be able to write unit tests which does not use the database. Your tests will be slow! Global test setup like this also tends to grow over time - it slows tests down and become very hard to maintain in the long run.

(I suffer badly from this in my current project, I have a bunch of "legacy" tests that uses global setup, that are really slow and they break all the time.)

I've only told you what to _not_ do yet. Here is what you can do instead. :-) A solution would be to use factory boy to help creating objects for tests. It makes it easy to get exactly the objects you need for a particular test, without having to resort to global setup. It helps making complicated test setup explicit, easy and at the same time avoiding global fixtures.

Hope this helps!

is this the best way to test django project/app w/ different settings?

import pytest
from django.conf import settings

@pytest.fixture
def basic_settings():
    settings... # change something in settings

@pytest.fixture
def other_settings():
    settings... # change something in settings

def test_basic(basic_settings):
    pass

def test_other(other_settings):
    pass

There is the settings fixture which can be used to automatically restore settings:

@pytest.fixture
def basic_settings(settings):
    settings.SOME_SETTING = 1234
    # SOME_SETTING will be automatically restored

This only works for _some_ settings. Certain settings have side effects that cannot really be undone because of how they are used within Django and other applications. An example of this is INSTALLED_APPS - once the apps has been loaded, there is not really any way to change it.

So depending on what settings you aim to change - this might or might not work, but that is just how Django works.

Unfortunately the settings fixture is not yet documented, I've opened #36 to not forget it.

if that would be in the docs, it would safe people hours of failure until they find this issue here. :)

@pytest.fixture(autouse=True)  # only works with function scope (default)
def project_setup(db):  # the db argument will make sure the database access is requested for this fixture
    User.objects.create(username="foo")

@ThomasWaldmann

It would be great to have that example in the documentation.

Maybe there should be a page in the documentation with examples/common patterns? I think examples like this would fit nicely.

A pull request that adds this example to the docs would be great!

This is quite old, but I had a similar problem and found a work-around that worked for me, so in case someone else stumbles across this:

@pytest.fixture(autouse=True, scope='session')
def post_db_setup(_django_db_setup, _django_cursor_wrapper):
    with _django_cursor_wrapper:
        call_command('loaddata', *settings.STATIC_FIXTURES, interactive=False)

I realize fixtures are not the preferred way to do things, but if you _really_ need to fill your DB with some values before any of the tests run... and only once, then that works quite well. It makes sure _django_db_setup has been done and you get _django_cursor_wrapper to write to it (I suspect any model creation would work in that context, but haven't tried it).

Have exactly the same problem, kind of frustrating. Will be great if this is documented, it's really a common pattern to populate a DB before running tests, and later cleanup files, attachments, logs, etc. :)

The current updated solution for pytest==3.0.7 pytest-django==3.1.2

@pytest.fixture(scope='session')
def populate_nodes(request, django_db_setup, django_db_blocker):
    from project.app.models import Attachment
    call_command('populate', verbosity=0, interactive=False)

    files = list(Attachment.objects.all().values_list('file', flat=True))
    django_db_blocker.unblock()

    # Cleanup
    def teardown():
        for file_path in files:
            path, ext = os.path.splitext(file_path)
            for path in glob.glob(f'{path}*'):
                os.unlink(path)

        django_db_blocker.restore()

    request.addfinalizer(teardown)
    return request

@mariocesar Did you see this example in the docs?

https://pytest-django.readthedocs.io/en/latest/database.html#populate-the-database-with-initial-test-data

I'm closing the issue for now (it was supposed to be closed when that example was added). Please reopen with additional details or open another issue if this needs to be further clarified or improved!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tolomea picture tolomea  路  6Comments

blueyed picture blueyed  路  7Comments

dan-passaro picture dan-passaro  路  4Comments

ojake picture ojake  路  6Comments

mjk4 picture mjk4  路  4Comments