This project seeks to assist asserting the number of queries per model during testing.
Note: This does so by monkey-patching the SQLCompiler
classes. It's
not something that should be relied upon in production.
pip install django-assert-model-queries
There are integrations for both pytest and Django / unittest
. Both of
which use the context manager,
django_assert_model_queries.AssertModelQueries
under the
hood.
The basic usage is to define a dictionary of expected queries to be evaluated at the end of the context manager's scope. If the counts differ, a helpful error message will be rendered indicating what the differences were and what all the queries were during the context.
from django_assert_model_queries import AssertModelQueries
from testapp.models import Community
with AssertModelQueries({"testapp.Community": 2}):
Community.objects.create(name="test")
Community.objects.update(name="test")
When an unexpected query runs, this AssertModelQueries
will
tell you which model generated an unexpected query.
Here is an example of what you can expect from the tool:
>>> from django_assert_model_queries import AssertModelQueries
>>> from django.contrib.auth.models import User
>>> with AssertModelQueries({}):
>>> User.objects.first()
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
Cell In[1], line 3
1 from django_assert_model_queries import AssertModelQueries
2 from django.contrib.auth.models import User
----> 3 with AssertModelQueries({}):
4 User.objects.only("id").first()
File ~/site-packages/django_assert_model_queries/test.py:145, in AssertModelQueries.__exit__(self, exc_type, exc_value, traceback)
142 if exc_type is not None:
143 return
--> 145 self.handle_assertion(actual, expected)
146 self.expected_model_counts = None
File ~/site-packages/django_assert_model_queries/test.py:172, in AssertModelQueries.handle_assertion(self, actual, expected)
170 pytest.fail(self.failure_message(actual, expected))
171 else:
--> 172 assert actual == expected, self.failure_message(actual, expected)
AssertionError: {'auth.User': 1} != {}
- {'auth.User': 1}
+ {}
All queries:
SELECT "auth_user"."id" FROM "auth_user" ORDER BY "auth_user"."id" ASC LIMIT 1
If you use pytest, you can use the fixture assert_model_queries
as a short-cut.
# pytest example
import pytest
from testapp.models import Community
class TestPytestIntegration:
@pytest.mark.django_db
def test_assert_model_queries(self, assert_model_queries):
with assert_model_queries({"testapp.Community": 1}):
Community.objects.create(name="test")
with assert_model_queries({
"testapp.Community": 2,
"testapp.Chapter": 1,
"testapp.Community_topics": 1,
}):
Community.objects.all().delete()
If you test with Django's TestCase
, inherit from the mixin
ModelNumQueriesHelper
to be able to utilize the
self.assertModelQueries
assertion method.
# Django TestCase example
from django.test import TestCase
from django_assert_model_queries import AssertModelQueries, ModelNumQueriesHelper
from testapp.models import Community
class TestDjangoIntegration(ModelNumQueriesHelper, TestCase):
def test_assert_model_num_queries_context(self):
with AssertModelQueries({"testapp.Community": 1}):
Community.objects.create(name="test")
with AssertModelQueries({"testapp.Community": 2, "testapp.Chapter": 1, "testapp.Community_topics": 1}):
Community.objects.all().delete()
class TestDjangoHelperIntegration(ModelNumQueriesHelper, TestCase):
def test_helper(self):
with self.assertModelQueries({"testapp.Community": 1}):
Community.objects.create(name="test")
with self.assertModelQueries({"testapp.Community": 2, "testapp.Chapter": 1, "testapp.Community_topics": 1}):
Community.objects.all().delete()
There are a few parameters that may help in certain scenarios.
ignore
: A collection of models that should be ignored. For example, maybe you want to ignoreSession
queries if you're using a database backed session.strict=False
: You can limit the count evaluation to just the models specified in theexpect_model_counts
collection. Be warned, this can hide N+1 issues.
To use these, you must specify them when instantiating
AssertModelQueries
.
from django_assert_model_queries import AssertModelQueries
from django.contrib.sessions.models import Session
assert_context = AssertModelQueries(ignore={Session})
with assert_context({"testapp.Community": 1}):
do_something()
assert_context = AssertModelQueries(strict=False)
with assert_context({"testapp.Community": 1}):
do_something()