-
Notifications
You must be signed in to change notification settings - Fork 346
Add initial approach to put Django's asserts into pytest's namespace. Issue #97 (was: Pull Request #144) #232
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
Changes from all commits
35e5786
e2e54f7
a7b9372
f563cec
8da7215
be866f9
bad3b09
87d86d6
e2cf3de
7253d00
dd9cfca
d878e64
58e3062
9dc1724
681c5a9
824299d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,6 @@ | |
This plugin handles creating and destroying the test environment and | ||
test database and provides some useful text fixtures. | ||
""" | ||
|
||
import contextlib | ||
import inspect | ||
from functools import reduce | ||
|
@@ -40,10 +39,96 @@ | |
SETTINGS_MODULE_ENV = 'DJANGO_SETTINGS_MODULE' | ||
CONFIGURATION_ENV = 'DJANGO_CONFIGURATION' | ||
INVALID_TEMPLATE_VARS_ENV = 'FAIL_INVALID_TEMPLATE_VARS' | ||
|
||
DJANGO_ASSERTS = { | ||
# from https://github.com/django/django/blob/stable/1.3.x/django/test/testcases.py#L244 | ||
(1, 3): ('assertContains', 'assertFormError', 'assertNotContains', | ||
'assertNumQueries', 'assertQuerysetEqual', 'assertRedirects', | ||
'assertTemplateNotUsed', 'assertTemplateUsed'), | ||
# from https://github.com/django/django/blob/stable/1.4.x/django/test/testcases.py#L448 | ||
(1, 4): ('assertContains', 'assertFieldOutput', 'assertFormError', | ||
'assertHTMLEqual', 'assertHTMLNotEqual', 'assertNotContains', | ||
'assertNumQueries', 'assertQuerysetEqual', 'assertRaisesMessage', | ||
'assertRedirects', 'assertTemplateNotUsed', 'assertTemplateUsed'), | ||
# from https://github.com/django/django/blob/stable/1.5.x/django/test/testcases.py#L246 | ||
# and https://github.com/django/django/blob/stable/1.5.x/django/test/testcases.py#L458 | ||
(1, 5): ('assertContains', 'assertFieldOutput', 'assertFormError', | ||
'assertHTMLEqual', 'assertHTMLNotEqual', 'assertInHTML', | ||
'assertJSONEqual', 'assertNotContains', 'assertNumQueries', | ||
'assertQuerysetEqual', 'assertRaisesMessage', 'assertRedirects', | ||
'assertTemplateNotUsed', 'assertTemplateUsed', 'assertXMLEqual', | ||
'assertXMLNotEqual'), | ||
# from https://github.com/django/django/blob/stable/1.6.x/django/test/testcases.py#L156 | ||
# and https://github.com/django/django/blob/stable/1.6.x/django/test/testcases.py#L725 | ||
(1, 6): ('assertContains', 'assertFieldOutput', 'assertFormError', | ||
'assertFormsetError', 'assertHTMLEqual', 'assertHTMLNotEqual', | ||
'assertInHTML', 'assertJSONEqual', 'assertNotContains', | ||
'assertNumQueries', 'assertQuerysetEqual', 'assertRaisesMessage', | ||
'assertRedirects', 'assertTemplateNotUsed', 'assertTemplateUsed', | ||
'assertXMLEqual', 'assertXMLNotEqual'), | ||
# from https://github.com/django/django/blob/stable/1.7.x/django/test/testcases.py#L162 | ||
# and https://github.com/django/django/blob/stable/1.7.x/django/test/testcases.py#L722 | ||
(1, 7): ('assertContains', 'assertFieldOutput', 'assertFormError', | ||
'assertFormsetError', 'assertHTMLEqual', 'assertHTMLNotEqual', | ||
'assertInHTML', 'assertJSONEqual', 'assertNotContains', | ||
'assertNumQueries', 'assertQuerysetEqual', 'assertRaisesMessage', | ||
'assertRedirects', 'assertTemplateNotUsed', 'assertTemplateUsed', | ||
'assertXMLEqual', 'assertXMLNotEqual'), | ||
# from https://github.com/django/django/blob/stable/1.8.x/django/test/testcases.py#L142 | ||
# and https://github.com/django/django/blob/stable/1.8.x/django/test/testcases.py#L751 | ||
(1, 8): ('assertContains', 'assertFieldOutput', 'assertFormError', | ||
'assertFormsetError', 'assertHTMLEqual', 'assertHTMLNotEqual', | ||
'assertInHTML', 'assertJSONEqual', 'assertJSONNotEqual', | ||
'assertNotContains', 'assertNumQueries', 'assertQuerysetEqual', | ||
'assertRaisesMessage', 'assertRedirects', 'assertTemplateNotUsed', | ||
'assertTemplateUsed', 'assertXMLEqual', 'assertXMLNotEqual'), | ||
# from https://github.com/django/django/blob/stable/1.9.x/django/test/testcases.py#L158 | ||
# and https://github.com/django/django/blob/stable/1.9.x/django/test/testcases.py#L818 | ||
(1, 9): ('assertContains', 'assertFieldOutput', 'assertFormError', | ||
'assertFormsetError', 'assertHTMLEqual', 'assertHTMLNotEqual', | ||
'assertInHTML', 'assertJSONEqual', 'assertJSONNotEqual', | ||
'assertNotContains', 'assertNumQueries', 'assertQuerysetEqual', | ||
'assertRaisesMessage', 'assertRedirects', 'assertTemplateNotUsed', | ||
'assertTemplateUsed', 'assertXMLEqual', 'assertXMLNotEqual'), | ||
# from https://github.com/django/django/blob/stable/1.10.x/django/test/testcases.py#L155 | ||
# and https://github.com/django/django/blob/stable/1.10.x/django/test/testcases.py#L797 | ||
(1, 10): ('assertContains', 'assertFieldOutput', 'assertFormError', | ||
'assertFormsetError', 'assertHTMLEqual', 'assertHTMLNotEqual', | ||
'assertInHTML', 'assertJSONEqual', 'assertJSONNotEqual', | ||
'assertNotContains', 'assertNumQueries', 'assertQuerysetEqual', | ||
'assertRaisesMessage', 'assertRedirects', 'assertTemplateNotUsed', | ||
'assertTemplateUsed', 'assertXMLEqual', 'assertXMLNotEqual'), | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These can be extracted dyanmically with e.g In [3]: {attr for attr in SimpleTestCase.__dict__ if attr.startswith('assert')}
Out[3]:
{'assertContains',
'assertFieldOutput',
'assertFormError',
'assertFormsetError',
'assertHTMLEqual',
'assertHTMLNotEqual',
'assertInHTML',
'assertJSONEqual',
'assertJSONNotEqual',
'assertNotContains',
'assertRaisesMessage',
'assertRedirects',
'assertTemplateNotUsed',
'assertTemplateUsed',
'assertXMLEqual',
'assertXMLNotEqual'} This works because a class's You probably need to look at |
||
|
||
# ############### pytest hooks ################ | ||
|
||
def populate_namespace(): | ||
def _wrapper(name): | ||
|
||
def assertion_func(*args, **kwargs): | ||
from django.test import TestCase as DjangoTestCase | ||
|
||
getattr(DjangoTestCase('run'), name)(*args, **kwargs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does |
||
|
||
return assertion_func | ||
|
||
asserts = {} | ||
for name in DJANGO_ASSERTS[get_django_version()[:2]]: | ||
asserts[name] = _wrapper(name) | ||
|
||
return {'django': asserts} | ||
|
||
|
||
def pytest_namespace(): | ||
"""Make unittest assert methods available. | ||
Useful for things such as floating point checks with assertAlmostEqual. | ||
""" | ||
try: | ||
django_settings_is_configured() | ||
except AssertionError: | ||
return populate_namespace() | ||
return {} | ||
|
||
|
||
def pytest_addoption(parser): | ||
group = parser.getgroup('django') | ||
group._addoption('--reuse-db', | ||
|
@@ -594,8 +679,7 @@ def _save_active_wrapper(self): | |
return self._history.append(self._dj_db_wrapper.ensure_connection) | ||
|
||
def _blocking_wrapper(*args, **kwargs): | ||
__tracebackhide__ = True | ||
__tracebackhide__ # Silence pyflakes | ||
__tracebackhide__ = True # noqa | ||
pytest.fail('Database access not allowed, ' | ||
'use the "django_db" mark, or the ' | ||
'"db" or "transactional_db" fixtures to enable it.') | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# coding: utf-8 | ||
import inspect | ||
|
||
import pytest | ||
import pytest_django | ||
|
||
from pytest_django.lazy_django import get_django_version | ||
from pytest_django.plugin import DJANGO_ASSERTS | ||
|
||
|
||
def _get_actual_assertions_names(): | ||
""" | ||
Returns list with names of all assertion helpers in Django. | ||
""" | ||
from django.test import TestCase as DjangoTestCase | ||
from django.utils.unittest import TestCase as DefaultTestCase | ||
|
||
obj = DjangoTestCase('run') | ||
is_assert = lambda x: x.startswith('assert') and '_' not in x | ||
base_methods = [name for name, member in | ||
inspect.getmembers(DefaultTestCase) | ||
if is_assert(name)] | ||
|
||
return [name for name, member in inspect.getmembers(obj) | ||
if is_assert(name) and name not in base_methods] | ||
|
||
|
||
def test_django_asserts_available(): | ||
django_assertions = _get_actual_assertions_names() | ||
expected_assertions = DJANGO_ASSERTS[get_django_version()[:2]] | ||
assert set(django_assertions) == set(expected_assertions) | ||
|
||
for name in expected_assertions: | ||
assert hasattr(pytest_django, name) | ||
|
||
|
||
def test_sanity(admin_client): | ||
from pytest_django import assertContains | ||
|
||
response = admin_client.get('/admin-required/') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this test is necessarily slow. |
||
|
||
assertContains(response, 'You are an admin') | ||
with pytest.raises(AssertionError): | ||
assertContains(response, 'Access denied') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of the sets are equal, maybe it could be moved into some constant?!
I.e. one for Django 1.8 and than use it for 1.9 and 1.10 also?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While the duplication here is not exactly accidental, I think it's save to say that it's merely coincidental. There is most probably no deep underlying principle behind what functions are shared between several versions and which aren't.
Thus, extracting the common function names to eliminate duplication might make this configuration less clear and maintainable instead of more so. (cf. Beware the Share by Udi Dahan
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment, they can be formed dynamically rather than using any duplication in the file or from django itself.