Skip to content

Add fixture for asserting maximum number of database queries #547

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 3 commits into from
Jul 27, 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
32 changes: 30 additions & 2 deletions docs/helpers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,13 @@ Example
``django_assert_num_queries``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. fixture:: django_assert_num_queries

This fixture allows to check for an expected number of DB queries.
It currently only supports the default database.

It wraps `django.test.utils.CaptureQueriesContext`. A non-default DB
connection can be passed in using the `connection` keyword argument, and it
will yield the wrapped CaptureQueriesContext instance.


Example
Expand All @@ -259,11 +264,34 @@ Example
::

def test_queries(django_assert_num_queries):
with django_assert_num_queries(3):
with django_assert_num_queries(3) as captured:
Item.objects.create('foo')
Item.objects.create('bar')
Item.objects.create('baz')

assert 'foo' in captured.captured_queries[0]['sql']


``django_assert_max_num_queries``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. fixture:: django_assert_max_num_queries

This fixture allows to check for an expected maximum number of DB queries.

It is a specialized version of :fixture:`django_assert_num_queries`.


Example
"""""""

::

def test_max_queries(django_assert_max_num_queries):
with django_assert_max_num_queries(3):
Item.objects.create('foo')
Item.objects.create('bar')


``mailoutbox``
~~~~~~~~~~~~~~
Expand Down
53 changes: 36 additions & 17 deletions pytest_django/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import with_statement

import os
from functools import partial

import pytest

Expand All @@ -17,7 +18,8 @@
__all__ = ['django_db_setup', 'db', 'transactional_db', 'admin_user',
'django_user_model', 'django_username_field',
'client', 'admin_client', 'rf', 'settings', 'live_server',
'_live_server_helper', 'django_assert_num_queries']
'_live_server_helper', 'django_assert_num_queries',
'django_assert_max_num_queries']


@pytest.fixture(scope='session')
Expand Down Expand Up @@ -352,22 +354,39 @@ def _live_server_helper(request):
request.addfinalizer(live_server._live_server_modified_settings.disable)


@contextmanager
def _assert_num_queries(config, num, exact=True, connection=None):
from django.test.utils import CaptureQueriesContext

if connection is None:
from django.db import connection

verbose = config.getoption('verbose') > 0
with CaptureQueriesContext(connection) as context:
yield context
num_queries = len(context)
failed = num != num_queries if exact else num < num_queries
if failed:
msg = "Expected to perform {} queries {}{}".format(
num,
'' if exact else 'or less ',
'but {} done'.format(
num_queries == 1 and '1 was' or '%d were' % (num_queries,)
)
)
if verbose:
sqls = (q['sql'] for q in context.captured_queries)
msg += '\n\nQueries:\n========\n\n%s' % '\n\n'.join(sqls)
else:
msg += " (add -v option to show queries)"
pytest.fail(msg)


@pytest.fixture(scope='function')
def django_assert_num_queries(pytestconfig):
from django.db import connection
from django.test.utils import CaptureQueriesContext
return partial(_assert_num_queries, pytestconfig)

@contextmanager
def _assert_num_queries(num):
with CaptureQueriesContext(connection) as context:
yield
if num != len(context):
msg = "Expected to perform %s queries but %s were done" % (num, len(context))
if pytestconfig.getoption('verbose') > 0:
sqls = (q['sql'] for q in context.captured_queries)
msg += '\n\nQueries:\n========\n\n%s' % '\n\n'.join(sqls)
else:
msg += " (add -v option to show queries)"
pytest.fail(msg)

return _assert_num_queries

@pytest.fixture(scope='function')
def django_assert_max_num_queries(pytestconfig):
return partial(_assert_num_queries, pytestconfig, exact=False)
1 change: 1 addition & 0 deletions pytest_django/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from .django_compat import is_django_unittest # noqa
from .fixtures import django_assert_num_queries # noqa
from .fixtures import django_assert_max_num_queries # noqa
from .fixtures import django_db_setup # noqa
from .fixtures import django_db_use_migrations # noqa
from .fixtures import django_db_keepdb # noqa
Expand Down
45 changes: 42 additions & 3 deletions tests/test_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,37 @@ def test_django_assert_num_queries_db(django_assert_num_queries):
Item.objects.create(name='bar')
Item.objects.create(name='baz')

with pytest.raises(pytest.fail.Exception):
with django_assert_num_queries(2):
with pytest.raises(pytest.fail.Exception) as excinfo:
with django_assert_num_queries(2) as captured:
Item.objects.create(name='quux')
assert excinfo.value.args == (
'Expected to perform 2 queries but 1 was done '
'(add -v option to show queries)',)
assert len(captured.captured_queries) == 1


@pytest.mark.django_db
def test_django_assert_max_num_queries_db(django_assert_max_num_queries):
with django_assert_max_num_queries(2):
Item.objects.create(name='1-foo')
Item.objects.create(name='2-bar')

with pytest.raises(pytest.fail.Exception) as excinfo:
with django_assert_max_num_queries(2) as captured:
Item.objects.create(name='1-foo')
Item.objects.create(name='2-bar')
Item.objects.create(name='3-quux')

assert excinfo.value.args == (
'Expected to perform 2 queries or less but 3 were done '
'(add -v option to show queries)',)
assert len(captured.captured_queries) == 3
assert '1-foo' in captured.captured_queries[0]['sql']


@pytest.mark.django_db(transaction=True)
def test_django_assert_num_queries_transactional_db(transactional_db, django_assert_num_queries):
def test_django_assert_num_queries_transactional_db(
transactional_db, django_assert_num_queries):
with transaction.atomic():

with django_assert_num_queries(3):
Expand Down Expand Up @@ -114,6 +138,21 @@ def test_queries(django_assert_num_queries):
assert result.ret == 1


@pytest.mark.django_db
def test_django_assert_num_queries_db_connection(django_assert_num_queries):
from django.db import connection

with django_assert_num_queries(1, connection=connection):
Item.objects.create(name='foo')

with django_assert_num_queries(1, connection=None):
Item.objects.create(name='foo')

with pytest.raises(AttributeError):
with django_assert_num_queries(1, connection=False):
pass


class TestSettings:
"""Tests for the settings fixture, order matters"""

Expand Down