Skip to content

Add initial approach to put Django's asserts into pytest's namespace. #97 #144

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

Closed
wants to merge 8 commits into from
1 change: 0 additions & 1 deletion pytest_django/lazy_django.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,5 @@ def django_settings_is_configured():
return True



def get_django_version():
return __import__('django').VERSION
67 changes: 64 additions & 3 deletions pytest_django/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
This plugin handles creating and destroying the test environment and
test database and provides some useful text fixtures.
"""

import os

import pytest
Expand All @@ -13,7 +12,8 @@
admin_client, rf, settings, live_server,
_live_server_helper)

from .lazy_django import skip_if_no_django, django_settings_is_configured
from .lazy_django import skip_if_no_django, django_settings_is_configured, \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better write this as from ... import ( ... ).

See https://github.com/timothycrosley/isort for a nice tool to sort imports automatically.

I use the following command with Vim:

command! -range=% Isort :<line1>,<line2>! isort -

/cc @pelme

get_django_version


(_django_db_setup, db, transactional_db, client, admin_client, rf,
Expand All @@ -22,10 +22,71 @@

SETTINGS_MODULE_ENV = 'DJANGO_SETTINGS_MODULE'
CONFIGURATION_ENV = 'DJANGO_CONFIGURATION'

DJANGO_ASSERTS = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add documentation / a comment here how to extract these, if you've used some tool / macro for this, please?

Probably somehow by using your _get_actual_assertions_names method probably?
This should provide/describe a convenient way to update/get the list in the future, e.g. based on a certain Django checkout.

(1, 3): ('assertContains', 'assertFormError', 'assertNotContains',
'assertNumQueries', 'assertQuerysetEqual', 'assertRedirects',
'assertTemplateNotUsed', 'assertTemplateUsed'),
(1, 4): ('assertContains', 'assertFieldOutput', 'assertFormError',
'assertHTMLEqual', 'assertHTMLNotEqual', 'assertNotContains',
'assertNumQueries', 'assertQuerysetEqual', 'assertRaisesMessage',
'assertRedirects', 'assertTemplateNotUsed', 'assertTemplateUsed'),
(1, 5): ('assertContains', 'assertFieldOutput', 'assertFormError',
'assertHTMLEqual', 'assertHTMLNotEqual', 'assertInHTML',
'assertJSONEqual', 'assertNotContains', 'assertNumQueries',
'assertQuerysetEqual', 'assertRaisesMessage', 'assertRedirects',
'assertTemplateNotUsed', 'assertTemplateUsed', 'assertXMLEqual',
'assertXMLNotEqual'),
(1, 6): ('assertContains', 'assertFieldOutput', 'assertFormError',
'assertFormsetError', 'assertHTMLEqual', 'assertHTMLNotEqual',
'assertInHTML', 'assertJSONEqual', 'assertNotContains',
'assertNumQueries', 'assertQuerysetEqual', 'assertRaisesMessage',
'assertRedirects', 'assertTemplateNotUsed', 'assertTemplateUsed',
'assertXMLEqual', 'assertXMLNotEqual'),
(1, 7): ('assertContains', 'assertFieldOutput', 'assertFormError',
'assertFormsetError', 'assertHTMLEqual', 'assertHTMLNotEqual',
'assertInHTML', 'assertJSONEqual', 'assertNotContains',
'assertNumQueries', 'assertQuerysetEqual', 'assertRaisesMessage',
'assertRedirects', 'assertTemplateNotUsed', 'assertTemplateUsed',
'assertXMLEqual', 'assertXMLNotEqual'),
(1, 8): ('assertContains', 'assertFieldOutput', 'assertFormError',
'assertFormsetError', 'assertHTMLEqual', 'assertHTMLNotEqual',
'assertInHTML', 'assertJSONEqual', 'assertJSONNotEqual',
'assertNotContains', 'assertNumQueries', 'assertQuerysetEqual',
'assertRaisesMessage', 'assertRedirects', 'assertTemplateNotUsed',
'assertTemplateUsed', 'assertXMLEqual', 'assertXMLNotEqual')
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it should be possible to collect these methods directly from django's SimpleTestCase, TestCase and TransactionTestCase classes rather than hardcoding.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with dynamically collecting these names is that Django may not be fully loaded when pytest_namespace is called.


################ 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)

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.
This is useful for things such floating point checks with assertAlmostEqual.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/such/such as/

"""
try:
django_settings_is_configured()
except AssertionError:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks odd. What assertion gets handled here, and why is this used to populate the namespace?

return populate_namespace()
return {}


def pytest_addoption(parser):
group = parser.getgroup('django')
group._addoption('--reuse-db',
Expand Down
42 changes: 42 additions & 0 deletions tests/test_namespaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# coding: utf-8
import inspect

import pytest

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 not set(django_assertions) ^ set(expected_assertions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this provide a useful diff on failure? Otherwise a normal comparison might be better.

Also set shouldn't be required here (otherwise please add a comment why it is), but only sorting them?


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/')

assertContains(response, 'You are an admin')
with pytest.raises(AssertionError):
assertContains(response, 'Access denied')