Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #20004 -- Moved non DB-related assertions to SimpleTestCase.

Thanks zalew for the suggestion and work on a patch.

Also updated, tweaked and fixed testing documentation.
  • Loading branch information...
commit 0a50311063c416ec4d39f518e8d8110dd7eddbdf 1 parent 69523c1
@ramiro ramiro authored
View
515 django/test/testcases.py
@@ -231,6 +231,10 @@ def message(self):
class SimpleTestCase(ut2.TestCase):
+ # The class we'll use for the test client self.client.
+ # Can be overridden in derived classes.
+ client_class = Client
+
_warn_txt = ("save_warnings_state/restore_warnings_state "
"django.test.*TestCase methods are deprecated. Use Python's "
"warnings.catch_warnings context manager instead.")
@@ -264,10 +268,31 @@ def __call__(self, result=None):
return
def _pre_setup(self):
- pass
+ """Performs any pre-test setup. This includes:
+
+ * If the Test Case class has a 'urls' member, replace the
+ ROOT_URLCONF with it.
+ * Clearing the mail test outbox.
+ """
+ self.client = self.client_class()
+ self._urlconf_setup()
+ mail.outbox = []
+
+ def _urlconf_setup(self):
+ set_urlconf(None)
+ if hasattr(self, 'urls'):
+ self._old_root_urlconf = settings.ROOT_URLCONF
+ settings.ROOT_URLCONF = self.urls
+ clear_url_caches()
def _post_teardown(self):
- pass
+ self._urlconf_teardown()
+
+ def _urlconf_teardown(self):
+ set_urlconf(None)
+ if hasattr(self, '_old_root_urlconf'):
+ settings.ROOT_URLCONF = self._old_root_urlconf
+ clear_url_caches()
def save_warnings_state(self):
"""
@@ -291,258 +316,6 @@ def settings(self, **kwargs):
"""
return override_settings(**kwargs)
- def assertRaisesMessage(self, expected_exception, expected_message,
- callable_obj=None, *args, **kwargs):
- """
- Asserts that the message in a raised exception matches the passed
- value.
-
- Args:
- expected_exception: Exception class expected to be raised.
- expected_message: expected error message string value.
- callable_obj: Function to be called.
- args: Extra args.
- kwargs: Extra kwargs.
- """
- return six.assertRaisesRegex(self, expected_exception,
- re.escape(expected_message), callable_obj, *args, **kwargs)
-
- def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
- field_kwargs=None, empty_value=''):
- """
- Asserts that a form field behaves correctly with various inputs.
-
- Args:
- fieldclass: the class of the field to be tested.
- valid: a dictionary mapping valid inputs to their expected
- cleaned values.
- invalid: a dictionary mapping invalid inputs to one or more
- raised error messages.
- field_args: the args passed to instantiate the field
- field_kwargs: the kwargs passed to instantiate the field
- empty_value: the expected clean output for inputs in empty_values
-
- """
- if field_args is None:
- field_args = []
- if field_kwargs is None:
- field_kwargs = {}
- required = fieldclass(*field_args, **field_kwargs)
- optional = fieldclass(*field_args,
- **dict(field_kwargs, required=False))
- # test valid inputs
- for input, output in valid.items():
- self.assertEqual(required.clean(input), output)
- self.assertEqual(optional.clean(input), output)
- # test invalid inputs
- for input, errors in invalid.items():
- with self.assertRaises(ValidationError) as context_manager:
- required.clean(input)
- self.assertEqual(context_manager.exception.messages, errors)
-
- with self.assertRaises(ValidationError) as context_manager:
- optional.clean(input)
- self.assertEqual(context_manager.exception.messages, errors)
- # test required inputs
- error_required = [force_text(required.error_messages['required'])]
- for e in required.empty_values:
- with self.assertRaises(ValidationError) as context_manager:
- required.clean(e)
- self.assertEqual(context_manager.exception.messages,
- error_required)
- self.assertEqual(optional.clean(e), empty_value)
- # test that max_length and min_length are always accepted
- if issubclass(fieldclass, CharField):
- field_kwargs.update({'min_length':2, 'max_length':20})
- self.assertTrue(isinstance(fieldclass(*field_args, **field_kwargs),
- fieldclass))
-
- def assertHTMLEqual(self, html1, html2, msg=None):
- """
- Asserts that two HTML snippets are semantically the same.
- Whitespace in most cases is ignored, and attribute ordering is not
- significant. The passed-in arguments must be valid HTML.
- """
- dom1 = assert_and_parse_html(self, html1, msg,
- 'First argument is not valid HTML:')
- dom2 = assert_and_parse_html(self, html2, msg,
- 'Second argument is not valid HTML:')
-
- if dom1 != dom2:
- standardMsg = '%s != %s' % (
- safe_repr(dom1, True), safe_repr(dom2, True))
- diff = ('\n' + '\n'.join(difflib.ndiff(
- six.text_type(dom1).splitlines(),
- six.text_type(dom2).splitlines())))
- standardMsg = self._truncateMessage(standardMsg, diff)
- self.fail(self._formatMessage(msg, standardMsg))
-
- def assertHTMLNotEqual(self, html1, html2, msg=None):
- """Asserts that two HTML snippets are not semantically equivalent."""
- dom1 = assert_and_parse_html(self, html1, msg,
- 'First argument is not valid HTML:')
- dom2 = assert_and_parse_html(self, html2, msg,
- 'Second argument is not valid HTML:')
-
- if dom1 == dom2:
- standardMsg = '%s == %s' % (
- safe_repr(dom1, True), safe_repr(dom2, True))
- self.fail(self._formatMessage(msg, standardMsg))
-
- def assertInHTML(self, needle, haystack, count = None, msg_prefix=''):
- needle = assert_and_parse_html(self, needle, None,
- 'First argument is not valid HTML:')
- haystack = assert_and_parse_html(self, haystack, None,
- 'Second argument is not valid HTML:')
- real_count = haystack.count(needle)
- if count is not None:
- self.assertEqual(real_count, count,
- msg_prefix + "Found %d instances of '%s' in response"
- " (expected %d)" % (real_count, needle, count))
- else:
- self.assertTrue(real_count != 0,
- msg_prefix + "Couldn't find '%s' in response" % needle)
-
- def assertJSONEqual(self, raw, expected_data, msg=None):
- try:
- data = json.loads(raw)
- except ValueError:
- self.fail("First argument is not valid JSON: %r" % raw)
- if isinstance(expected_data, six.string_types):
- try:
- expected_data = json.loads(expected_data)
- except ValueError:
- self.fail("Second argument is not valid JSON: %r" % expected_data)
- self.assertEqual(data, expected_data, msg=msg)
-
- def assertXMLEqual(self, xml1, xml2, msg=None):
- """
- Asserts that two XML snippets are semantically the same.
- Whitespace in most cases is ignored, and attribute ordering is not
- significant. The passed-in arguments must be valid XML.
- """
- try:
- result = compare_xml(xml1, xml2)
- except Exception as e:
- standardMsg = 'First or second argument is not valid XML\n%s' % e
- self.fail(self._formatMessage(msg, standardMsg))
- else:
- if not result:
- standardMsg = '%s != %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
- self.fail(self._formatMessage(msg, standardMsg))
-
- def assertXMLNotEqual(self, xml1, xml2, msg=None):
- """
- Asserts that two XML snippets are not semantically equivalent.
- Whitespace in most cases is ignored, and attribute ordering is not
- significant. The passed-in arguments must be valid XML.
- """
- try:
- result = compare_xml(xml1, xml2)
- except Exception as e:
- standardMsg = 'First or second argument is not valid XML\n%s' % e
- self.fail(self._formatMessage(msg, standardMsg))
- else:
- if result:
- standardMsg = '%s == %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
- self.fail(self._formatMessage(msg, standardMsg))
-
-
-class TransactionTestCase(SimpleTestCase):
-
- # The class we'll use for the test client self.client.
- # Can be overridden in derived classes.
- client_class = Client
-
- # Subclasses can ask for resetting of auto increment sequence before each
- # test case
- reset_sequences = False
-
- def _pre_setup(self):
- """Performs any pre-test setup. This includes:
-
- * Flushing the database.
- * If the Test Case class has a 'fixtures' member, installing the
- named fixtures.
- * If the Test Case class has a 'urls' member, replace the
- ROOT_URLCONF with it.
- * Clearing the mail test outbox.
- """
- self.client = self.client_class()
- self._fixture_setup()
- self._urlconf_setup()
- mail.outbox = []
-
- def _databases_names(self, include_mirrors=True):
- # If the test case has a multi_db=True flag, act on all databases,
- # including mirrors or not. Otherwise, just on the default DB.
- if getattr(self, 'multi_db', False):
- return [alias for alias in connections
- if include_mirrors or not connections[alias].settings_dict['TEST_MIRROR']]
- else:
- return [DEFAULT_DB_ALIAS]
-
- def _reset_sequences(self, db_name):
- conn = connections[db_name]
- if conn.features.supports_sequence_reset:
- sql_list = \
- conn.ops.sequence_reset_by_name_sql(no_style(),
- conn.introspection.sequence_list())
- if sql_list:
- with transaction.commit_on_success_unless_managed(using=db_name):
- cursor = conn.cursor()
- for sql in sql_list:
- cursor.execute(sql)
-
- def _fixture_setup(self):
- for db_name in self._databases_names(include_mirrors=False):
- # Reset sequences
- if self.reset_sequences:
- self._reset_sequences(db_name)
-
- if hasattr(self, 'fixtures'):
- # We have to use this slightly awkward syntax due to the fact
- # that we're using *args and **kwargs together.
- call_command('loaddata', *self.fixtures,
- **{'verbosity': 0, 'database': db_name, 'skip_validation': True})
-
- def _urlconf_setup(self):
- set_urlconf(None)
- if hasattr(self, 'urls'):
- self._old_root_urlconf = settings.ROOT_URLCONF
- settings.ROOT_URLCONF = self.urls
- clear_url_caches()
-
- def _post_teardown(self):
- """ Performs any post-test things. This includes:
-
- * Putting back the original ROOT_URLCONF if it was changed.
- * Force closing the connection, so that the next test gets
- a clean cursor.
- """
- self._fixture_teardown()
- self._urlconf_teardown()
- # Some DB cursors include SQL statements as part of cursor
- # creation. If you have a test that does rollback, the effect
- # of these statements is lost, which can effect the operation
- # of tests (e.g., losing a timezone setting causing objects to
- # be created with the wrong time).
- # To make sure this doesn't happen, get a clean connection at the
- # start of every test.
- for conn in connections.all():
- conn.close()
-
- def _fixture_teardown(self):
- for db in self._databases_names(include_mirrors=False):
- call_command('flush', verbosity=0, interactive=False, database=db,
- skip_validation=True, reset_sequences=False)
-
- def _urlconf_teardown(self):
- set_urlconf(None)
- if hasattr(self, '_old_root_urlconf'):
- settings.ROOT_URLCONF = self._old_root_urlconf
- clear_url_caches()
-
def assertRedirects(self, response, expected_url, status_code=302,
target_status_code=200, host=None, msg_prefix=''):
"""Asserts that a response redirected to a specific URL, and that the
@@ -787,6 +560,236 @@ def assertTemplateNotUsed(self, response=None, template_name=None, msg_prefix=''
msg_prefix + "Template '%s' was used unexpectedly in rendering"
" the response" % template_name)
+ def assertRaisesMessage(self, expected_exception, expected_message,
+ callable_obj=None, *args, **kwargs):
+ """
+ Asserts that the message in a raised exception matches the passed
+ value.
+
+ Args:
+ expected_exception: Exception class expected to be raised.
+ expected_message: expected error message string value.
+ callable_obj: Function to be called.
+ args: Extra args.
+ kwargs: Extra kwargs.
+ """
+ return six.assertRaisesRegex(self, expected_exception,
+ re.escape(expected_message), callable_obj, *args, **kwargs)
+
+ def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
+ field_kwargs=None, empty_value=''):
+ """
+ Asserts that a form field behaves correctly with various inputs.
+
+ Args:
+ fieldclass: the class of the field to be tested.
+ valid: a dictionary mapping valid inputs to their expected
+ cleaned values.
+ invalid: a dictionary mapping invalid inputs to one or more
+ raised error messages.
+ field_args: the args passed to instantiate the field
+ field_kwargs: the kwargs passed to instantiate the field
+ empty_value: the expected clean output for inputs in empty_values
+
+ """
+ if field_args is None:
+ field_args = []
+ if field_kwargs is None:
+ field_kwargs = {}
+ required = fieldclass(*field_args, **field_kwargs)
+ optional = fieldclass(*field_args,
+ **dict(field_kwargs, required=False))
+ # test valid inputs
+ for input, output in valid.items():
+ self.assertEqual(required.clean(input), output)
+ self.assertEqual(optional.clean(input), output)
+ # test invalid inputs
+ for input, errors in invalid.items():
+ with self.assertRaises(ValidationError) as context_manager:
+ required.clean(input)
+ self.assertEqual(context_manager.exception.messages, errors)
+
+ with self.assertRaises(ValidationError) as context_manager:
+ optional.clean(input)
+ self.assertEqual(context_manager.exception.messages, errors)
+ # test required inputs
+ error_required = [force_text(required.error_messages['required'])]
+ for e in required.empty_values:
+ with self.assertRaises(ValidationError) as context_manager:
+ required.clean(e)
+ self.assertEqual(context_manager.exception.messages,
+ error_required)
+ self.assertEqual(optional.clean(e), empty_value)
+ # test that max_length and min_length are always accepted
+ if issubclass(fieldclass, CharField):
+ field_kwargs.update({'min_length':2, 'max_length':20})
+ self.assertTrue(isinstance(fieldclass(*field_args, **field_kwargs),
+ fieldclass))
+
+ def assertHTMLEqual(self, html1, html2, msg=None):
+ """
+ Asserts that two HTML snippets are semantically the same.
+ Whitespace in most cases is ignored, and attribute ordering is not
+ significant. The passed-in arguments must be valid HTML.
+ """
+ dom1 = assert_and_parse_html(self, html1, msg,
+ 'First argument is not valid HTML:')
+ dom2 = assert_and_parse_html(self, html2, msg,
+ 'Second argument is not valid HTML:')
+
+ if dom1 != dom2:
+ standardMsg = '%s != %s' % (
+ safe_repr(dom1, True), safe_repr(dom2, True))
+ diff = ('\n' + '\n'.join(difflib.ndiff(
+ six.text_type(dom1).splitlines(),
+ six.text_type(dom2).splitlines())))
+ standardMsg = self._truncateMessage(standardMsg, diff)
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertHTMLNotEqual(self, html1, html2, msg=None):
+ """Asserts that two HTML snippets are not semantically equivalent."""
+ dom1 = assert_and_parse_html(self, html1, msg,
+ 'First argument is not valid HTML:')
+ dom2 = assert_and_parse_html(self, html2, msg,
+ 'Second argument is not valid HTML:')
+
+ if dom1 == dom2:
+ standardMsg = '%s == %s' % (
+ safe_repr(dom1, True), safe_repr(dom2, True))
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertInHTML(self, needle, haystack, count=None, msg_prefix=''):
+ needle = assert_and_parse_html(self, needle, None,
+ 'First argument is not valid HTML:')
+ haystack = assert_and_parse_html(self, haystack, None,
+ 'Second argument is not valid HTML:')
+ real_count = haystack.count(needle)
+ if count is not None:
+ self.assertEqual(real_count, count,
+ msg_prefix + "Found %d instances of '%s' in response"
+ " (expected %d)" % (real_count, needle, count))
+ else:
+ self.assertTrue(real_count != 0,
+ msg_prefix + "Couldn't find '%s' in response" % needle)
+
+ def assertJSONEqual(self, raw, expected_data, msg=None):
+ try:
+ data = json.loads(raw)
+ except ValueError:
+ self.fail("First argument is not valid JSON: %r" % raw)
+ if isinstance(expected_data, six.string_types):
+ try:
+ expected_data = json.loads(expected_data)
+ except ValueError:
+ self.fail("Second argument is not valid JSON: %r" % expected_data)
+ self.assertEqual(data, expected_data, msg=msg)
+
+ def assertXMLEqual(self, xml1, xml2, msg=None):
+ """
+ Asserts that two XML snippets are semantically the same.
+ Whitespace in most cases is ignored, and attribute ordering is not
+ significant. The passed-in arguments must be valid XML.
+ """
+ try:
+ result = compare_xml(xml1, xml2)
+ except Exception as e:
+ standardMsg = 'First or second argument is not valid XML\n%s' % e
+ self.fail(self._formatMessage(msg, standardMsg))
+ else:
+ if not result:
+ standardMsg = '%s != %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertXMLNotEqual(self, xml1, xml2, msg=None):
+ """
+ Asserts that two XML snippets are not semantically equivalent.
+ Whitespace in most cases is ignored, and attribute ordering is not
+ significant. The passed-in arguments must be valid XML.
+ """
+ try:
+ result = compare_xml(xml1, xml2)
+ except Exception as e:
+ standardMsg = 'First or second argument is not valid XML\n%s' % e
+ self.fail(self._formatMessage(msg, standardMsg))
+ else:
+ if result:
+ standardMsg = '%s == %s' % (safe_repr(xml1, True), safe_repr(xml2, True))
+ self.fail(self._formatMessage(msg, standardMsg))
+
+
+class TransactionTestCase(SimpleTestCase):
+
+ # Subclasses can ask for resetting of auto increment sequence before each
+ # test case
+ reset_sequences = False
+
+ def _pre_setup(self):
+ """Performs any pre-test setup. This includes:
+
+ * Flushing the database.
+ * If the Test Case class has a 'fixtures' member, installing the
+ named fixtures.
+ """
+ super(TransactionTestCase, self)._pre_setup()
+ self._fixture_setup()
+
+ def _databases_names(self, include_mirrors=True):
+ # If the test case has a multi_db=True flag, act on all databases,
+ # including mirrors or not. Otherwise, just on the default DB.
+ if getattr(self, 'multi_db', False):
+ return [alias for alias in connections
+ if include_mirrors or not connections[alias].settings_dict['TEST_MIRROR']]
+ else:
+ return [DEFAULT_DB_ALIAS]
+
+ def _reset_sequences(self, db_name):
+ conn = connections[db_name]
+ if conn.features.supports_sequence_reset:
+ sql_list = \
+ conn.ops.sequence_reset_by_name_sql(no_style(),
+ conn.introspection.sequence_list())
+ if sql_list:
+ with transaction.commit_on_success_unless_managed(using=db_name):
+ cursor = conn.cursor()
+ for sql in sql_list:
+ cursor.execute(sql)
+
+ def _fixture_setup(self):
+ for db_name in self._databases_names(include_mirrors=False):
+ # Reset sequences
+ if self.reset_sequences:
+ self._reset_sequences(db_name)
+
+ if hasattr(self, 'fixtures'):
+ # We have to use this slightly awkward syntax due to the fact
+ # that we're using *args and **kwargs together.
+ call_command('loaddata', *self.fixtures,
+ **{'verbosity': 0, 'database': db_name, 'skip_validation': True})
+
+ def _post_teardown(self):
+ """Performs any post-test things. This includes:
+
+ * Putting back the original ROOT_URLCONF if it was changed.
+ * Force closing the connection, so that the next test gets
+ a clean cursor.
+ """
+ self._fixture_teardown()
+ super(TransactionTestCase, self)._post_teardown()
+ # Some DB cursors include SQL statements as part of cursor
+ # creation. If you have a test that does rollback, the effect
+ # of these statements is lost, which can effect the operation
+ # of tests (e.g., losing a timezone setting causing objects to
+ # be created with the wrong time).
+ # To make sure this doesn't happen, get a clean connection at the
+ # start of every test.
+ for conn in connections.all():
+ conn.close()
+
+ def _fixture_teardown(self):
+ for db_name in self._databases_names(include_mirrors=False):
+ call_command('flush', verbosity=0, interactive=False, database=db_name,
+ skip_validation=True, reset_sequences=False)
+
def assertQuerysetEqual(self, qs, values, transform=repr, ordered=True):
items = six.moves.map(transform, qs)
if not ordered:
@@ -841,14 +844,14 @@ def _fixture_setup(self):
# Remove this when the legacy transaction management goes away.
disable_transaction_methods()
- for db in self._databases_names(include_mirrors=False):
+ for db_name in self._databases_names(include_mirrors=False):
if hasattr(self, 'fixtures'):
try:
call_command('loaddata', *self.fixtures,
**{
'verbosity': 0,
'commit': False,
- 'database': db,
+ 'database': db_name,
'skip_validation': True,
})
except Exception:
View
4 docs/intro/tutorial05.txt
@@ -503,8 +503,8 @@ of the process of creating polls.
message: "No polls are available." and verifies the ``latest_poll_list`` is
empty. Note that the :class:`django.test.TestCase` class provides some
additional assertion methods. In these examples, we use
-:meth:`~django.test.TestCase.assertContains()` and
-:meth:`~django.test.TestCase.assertQuerysetEqual()`.
+:meth:`~django.test.SimpleTestCase.assertContains()` and
+:meth:`~django.test.TransactionTestCase.assertQuerysetEqual()`.
In ``test_index_view_with_a_past_poll``, we create a poll and verify that it
appears in the list.
View
2  docs/ref/contrib/contenttypes.txt
@@ -329,7 +329,7 @@ model:
.. admonition:: Serializing references to ``ContentType`` objects
If you're serializing data (for example, when generating
- :class:`~django.test.TestCase.fixtures`) from a model that implements
+ :class:`~django.test.TransactionTestCase.fixtures`) from a model that implements
generic relations, you should probably be using a natural key to uniquely
identify related :class:`~django.contrib.contenttypes.models.ContentType`
objects. See :ref:`natural keys<topics-serialization-natural-keys>` and
View
2  docs/releases/1.3-alpha-1.txt
@@ -154,7 +154,7 @@ requests. These include:
requests in tests.
* A new test assertion --
- :meth:`~django.test.TestCase.assertNumQueries` -- making it
+ :meth:`~django.test.TransactionTestCase.assertNumQueries` -- making it
easier to test the database activity associated with a view.
View
2  docs/releases/1.3.txt
@@ -299,7 +299,7 @@ requests. These include:
in tests.
* A new test assertion --
- :meth:`~django.test.TestCase.assertNumQueries` -- making it
+ :meth:`~django.test.TransactionTestCase.assertNumQueries` -- making it
easier to test the database activity associated with a view.
* Support for lookups spanning relations in admin's
View
8 docs/releases/1.4.txt
@@ -541,8 +541,8 @@ compare HTML directly with the new
:meth:`~django.test.SimpleTestCase.assertHTMLEqual` and
:meth:`~django.test.SimpleTestCase.assertHTMLNotEqual` assertions, or use
the ``html=True`` flag with
-:meth:`~django.test.TestCase.assertContains` and
-:meth:`~django.test.TestCase.assertNotContains` to test whether the
+:meth:`~django.test.SimpleTestCase.assertContains` and
+:meth:`~django.test.SimpleTestCase.assertNotContains` to test whether the
client's response contains a given HTML fragment. See the :ref:`assertions
documentation <assertions>` for more.
@@ -1093,8 +1093,8 @@ wild, because they would confuse browsers too.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It's now possible to check whether a template was used within a block of
-code with :meth:`~django.test.TestCase.assertTemplateUsed` and
-:meth:`~django.test.TestCase.assertTemplateNotUsed`. And they
+code with :meth:`~django.test.SimpleTestCase.assertTemplateUsed` and
+:meth:`~django.test.SimpleTestCase.assertTemplateNotUsed`. And they
can be used as a context manager::
with self.assertTemplateUsed('index.html'):
View
7 docs/releases/1.6.txt
@@ -271,9 +271,10 @@ The changes in transaction management may result in additional statements to
create, release or rollback savepoints. This is more likely to happen with
SQLite, since it didn't support savepoints until this release.
-If tests using :meth:`~django.test.TestCase.assertNumQueries` fail because of
-a higher number of queries than expected, check that the extra queries are
-related to savepoints, and adjust the expected number of queries accordingly.
+If tests using :meth:`~django.test.TransactionTestCase.assertNumQueries` fail
+because of a higher number of queries than expected, check that the extra
+queries are related to savepoints, and adjust the expected number of queries
+accordingly.
Autocommit option for PostgreSQL
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
View
4 docs/topics/python3.txt
@@ -201,8 +201,8 @@ According to :pep:`3333`:
Specifically, :attr:`HttpResponse.content <django.http.HttpResponse.content>`
contains ``bytes``, which may become an issue if you compare it with a
``str`` in your tests. The preferred solution is to rely on
-:meth:`~django.test.TestCase.assertContains` and
-:meth:`~django.test.TestCase.assertNotContains`. These methods accept a
+:meth:`~django.test.SimpleTestCase.assertContains` and
+:meth:`~django.test.SimpleTestCase.assertNotContains`. These methods accept a
response and a unicode string as arguments.
Coding guidelines
View
235 docs/topics/testing/overview.txt
@@ -21,17 +21,16 @@ module defines tests using a class-based approach.
.. admonition:: unittest2
- Python 2.7 introduced some major changes to the unittest library,
+ Python 2.7 introduced some major changes to the ``unittest`` library,
adding some extremely useful features. To ensure that every Django
project can benefit from these new features, Django ships with a
- copy of unittest2_, a copy of the Python 2.7 unittest library,
- backported for Python 2.6 compatibility.
+ copy of unittest2_, a copy of Python 2.7's ``unittest``, backported for
+ Python 2.6 compatibility.
To access this library, Django provides the
``django.utils.unittest`` module alias. If you are using Python
- 2.7, or you have installed unittest2 locally, Django will map the
- alias to the installed version of the unittest library. Otherwise,
- Django will use its own bundled version of unittest2.
+ 2.7, or you have installed ``unittest2`` locally, Django will map the alias
+ to it. Otherwise, Django will use its own bundled version of ``unittest2``.
To use this alias, simply use::
@@ -41,8 +40,8 @@ module defines tests using a class-based approach.
import unittest
- If you want to continue to use the base unittest library, you can --
- you just won't get any of the nice new unittest2 features.
+ If you want to continue to use the legacy ``unittest`` library, you can --
+ you just won't get any of the nice new ``unittest2`` features.
.. _unittest2: http://pypi.python.org/pypi/unittest2
@@ -858,24 +857,46 @@ SimpleTestCase
.. class:: SimpleTestCase()
-A very thin subclass of :class:`unittest.TestCase`, it extends it with some
-basic functionality like:
+A thin subclass of :class:`unittest.TestCase`, it extends it with some basic
+functionality like:
* Saving and restoring the Python warning machinery state.
-* Checking that a callable :meth:`raises a certain exception <SimpleTestCase.assertRaisesMessage>`.
-* :meth:`Testing form field rendering <SimpleTestCase.assertFieldOutput>`.
-* Testing server :ref:`HTML responses for the presence/lack of a given fragment <assertions>`.
-* The ability to run tests with :ref:`modified settings <overriding-settings>`
+* Some useful assertions like:
+
+ * Checking that a callable :meth:`raises a certain exception
+ <SimpleTestCase.assertRaisesMessage>`.
+ * Testing form field :meth:`rendering and error treatment
+ <SimpleTestCase.assertFieldOutput>`.
+ * Testing :meth:`HTML responses for the presence/lack of a given fragment
+ <SimpleTestCase.assertContains>`.
+ * Verifying that a template :meth:`has/hasn't been used to generate a given
+ response content <SimpleTestCase.assertTemplateUsed>`.
+ * Verifying a HTTP :meth:`redirect <SimpleTestCase.assertRedirects>` is
+ performed by the app.
+ * Robustly testing two :meth:`HTML fragments <SimpleTestCase.assertHTMLEqual>`
+ for equality/inequality or :meth:`containment <SimpleTestCase.assertInHTML>`.
+ * Robustly testing two :meth:`XML fragments <SimpleTestCase.assertXMLEqual>`
+ for equality/inequality.
+ * Robustly testing two :meth:`JSON fragments <SimpleTestCase.assertJSONEqual>`
+ for equality.
+
+* The ability to run tests with :ref:`modified settings <overriding-settings>`.
+* Using the :attr:`~SimpleTestCase.client` :class:`~django.test.client.Client`.
+* Custom test-time :attr:`URL maps <SimpleTestCase.urls>`.
+
+.. versionchanged:: 1.6
+
+ The latter two features were moved from ``TransactionTestCase`` to
+ ``SimpleTestCase`` in Django 1.6.
If you need any of the other more complex and heavyweight Django-specific
features like:
-* Using the :attr:`~TestCase.client` :class:`~django.test.client.Client`.
* Testing or using the ORM.
-* Database :attr:`~TestCase.fixtures`.
-* Custom test-time :attr:`URL maps <TestCase.urls>`.
+* Database :attr:`~TransactionTestCase.fixtures`.
* Test :ref:`skipping based on database backend features <skipping-tests>`.
-* The remaining specialized :ref:`assert* <assertions>` methods.
+* The remaining specialized :meth:`assert*
+ <TransactionTestCase.assertQuerysetEqual>` methods.
then you should use :class:`~django.test.TransactionTestCase` or
:class:`~django.test.TestCase` instead.
@@ -1137,9 +1158,9 @@ Test cases features
Default test client
~~~~~~~~~~~~~~~~~~~
-.. attribute:: TestCase.client
+.. attribute:: SimpleTestCase.client
-Every test case in a ``django.test.TestCase`` instance has access to an
+Every test case in a ``django.test.*TestCase`` instance has access to an
instance of a Django test client. This client can be accessed as
``self.client``. This client is recreated for each test, so you don't have to
worry about state (such as cookies) carrying over from one test to another.
@@ -1176,10 +1197,10 @@ This means, instead of instantiating a ``Client`` in each test::
Customizing the test client
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. attribute:: TestCase.client_class
+.. attribute:: SimpleTestCase.client_class
If you want to use a different ``Client`` class (for example, a subclass
-with customized behavior), use the :attr:`~TestCase.client_class` class
+with customized behavior), use the :attr:`~SimpleTestCase.client_class` class
attribute::
from django.test import TestCase
@@ -1200,11 +1221,12 @@ attribute::
Fixture loading
~~~~~~~~~~~~~~~
-.. attribute:: TestCase.fixtures
+.. attribute:: TransactionTestCase.fixtures
A test case for a database-backed Web site isn't much use if there isn't any
data in the database. To make it easy to put test data into the database,
-Django's custom ``TestCase`` class provides a way of loading **fixtures**.
+Django's custom ``TransactionTestCase`` class provides a way of loading
+**fixtures**.
A fixture is a collection of data that Django knows how to import into a
database. For example, if your site has user accounts, you might set up a
@@ -1273,7 +1295,7 @@ or by the order of test execution.
URLconf configuration
~~~~~~~~~~~~~~~~~~~~~
-.. attribute:: TestCase.urls
+.. attribute:: SimpleTestCase.urls
If your application provides views, you may want to include tests that use the
test client to exercise those views. However, an end user is free to deploy the
@@ -1282,9 +1304,9 @@ tests can't rely upon the fact that your views will be available at a
particular URL.
In order to provide a reliable URL space for your test,
-``django.test.TestCase`` provides the ability to customize the URLconf
+``django.test.*TestCase`` classes provide the ability to customize the URLconf
configuration for the duration of the execution of a test suite. If your
-``TestCase`` instance defines an ``urls`` attribute, the ``TestCase`` will use
+``*TestCase`` instance defines an ``urls`` attribute, the ``*TestCase`` will use
the value of that attribute as the :setting:`ROOT_URLCONF` for the duration
of that test.
@@ -1307,7 +1329,7 @@ URLconf for the duration of the test case.
Multi-database support
~~~~~~~~~~~~~~~~~~~~~~
-.. attribute:: TestCase.multi_db
+.. attribute:: TransactionTestCase.multi_db
Django sets up a test database corresponding to every database that is
defined in the :setting:`DATABASES` definition in your settings
@@ -1340,12 +1362,12 @@ This test case will flush *all* the test databases before running
Overriding settings
~~~~~~~~~~~~~~~~~~~
-.. method:: TestCase.settings
+.. method:: SimpleTestCase.settings
For testing purposes it's often useful to change a setting temporarily and
revert to the original value after running the testing code. For this use case
Django provides a standard Python context manager (see :pep:`343`)
-:meth:`~django.test.TestCase.settings`, which can be used like this::
+:meth:`~django.test.SimpleTestCase.settings`, which can be used like this::
from django.test import TestCase
@@ -1435,8 +1457,8 @@ MEDIA_ROOT, DEFAULT_FILE_STORAGE Default file storage
Emptying the test outbox
~~~~~~~~~~~~~~~~~~~~~~~~
-If you use Django's custom ``TestCase`` class, the test runner will clear the
-contents of the test email outbox at the start of each test case.
+If you use any of Django's custom ``TestCase`` classes, the test runner will
+clear thecontents of the test email outbox at the start of each test case.
For more detail on email services during tests, see `Email services`_ below.
@@ -1486,8 +1508,22 @@ your test suite.
self.assertFieldOutput(EmailField, {'a@a.com': 'a@a.com'}, {'aaa': [u'Enter a valid email address.']})
+.. method:: SimpleTestCase.assertFormError(response, form, field, errors, msg_prefix='')
+
+ Asserts that a field on a form raises the provided list of errors when
+ rendered on the form.
+
+ ``form`` is the name the ``Form`` instance was given in the template
+ context.
+
+ ``field`` is the name of the field on the form to check. If ``field``
+ has a value of ``None``, non-field errors (errors you can access via
+ ``form.non_field_errors()``) will be checked.
+
+ ``errors`` is an error string, or a list of error strings, that are
+ expected as a result of form validation.
-.. method:: TestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)
+.. method:: SimpleTestCase.assertContains(response, text, count=None, status_code=200, msg_prefix='', html=False)
Asserts that a ``Response`` instance produced the given ``status_code`` and
that ``text`` appears in the content of the response. If ``count`` is
@@ -1499,7 +1535,7 @@ your test suite.
attribute ordering is not significant. See
:meth:`~SimpleTestCase.assertHTMLEqual` for more details.
-.. method:: TestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)
+.. method:: SimpleTestCase.assertNotContains(response, text, status_code=200, msg_prefix='', html=False)
Asserts that a ``Response`` instance produced the given ``status_code`` and
that ``text`` does not appears in the content of the response.
@@ -1510,22 +1546,7 @@ your test suite.
attribute ordering is not significant. See
:meth:`~SimpleTestCase.assertHTMLEqual` for more details.
-.. method:: TestCase.assertFormError(response, form, field, errors, msg_prefix='')
-
- Asserts that a field on a form raises the provided list of errors when
- rendered on the form.
-
- ``form`` is the name the ``Form`` instance was given in the template
- context.
-
- ``field`` is the name of the field on the form to check. If ``field``
- has a value of ``None``, non-field errors (errors you can access via
- ``form.non_field_errors()``) will be checked.
-
- ``errors`` is an error string, or a list of error strings, that are
- expected as a result of form validation.
-
-.. method:: TestCase.assertTemplateUsed(response, template_name, msg_prefix='')
+.. method:: SimpleTestCase.assertTemplateUsed(response, template_name, msg_prefix='')
Asserts that the template with the given name was used in rendering the
response.
@@ -1539,15 +1560,15 @@ your test suite.
with self.assertTemplateUsed(template_name='index.html'):
render_to_string('index.html')
-.. method:: TestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')
+.. method:: SimpleTestCase.assertTemplateNotUsed(response, template_name, msg_prefix='')
Asserts that the template with the given name was *not* used in rendering
the response.
You can use this as a context manager in the same way as
- :meth:`~TestCase.assertTemplateUsed`.
+ :meth:`~SimpleTestCase.assertTemplateUsed`.
-.. method:: TestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='')
+.. method:: SimpleTestCase.assertRedirects(response, expected_url, status_code=302, target_status_code=200, msg_prefix='')
Asserts that the response return a ``status_code`` redirect status, it
redirected to ``expected_url`` (including any GET data), and the final
@@ -1557,44 +1578,6 @@ your test suite.
``target_status_code`` will be the url and status code for the final
point of the redirect chain.
-.. method:: TestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True)
-
- Asserts that a queryset ``qs`` returns a particular list of values ``values``.
-
- The comparison of the contents of ``qs`` and ``values`` is performed using
- the function ``transform``; by default, this means that the ``repr()`` of
- each value is compared. Any other callable can be used if ``repr()`` doesn't
- provide a unique or helpful comparison.
-
- By default, the comparison is also ordering dependent. If ``qs`` doesn't
- provide an implicit ordering, you can set the ``ordered`` parameter to
- ``False``, which turns the comparison into a Python set comparison.
-
- .. versionchanged:: 1.6
-
- The method now checks for undefined order and raises ``ValueError``
- if undefined order is spotted. The ordering is seen as undefined if
- the given ``qs`` isn't ordered and the comparison is against more
- than one ordered values.
-
-.. method:: TestCase.assertNumQueries(num, func, *args, **kwargs)
-
- Asserts that when ``func`` is called with ``*args`` and ``**kwargs`` that
- ``num`` database queries are executed.
-
- If a ``"using"`` key is present in ``kwargs`` it is used as the database
- alias for which to check the number of queries. If you wish to call a
- function with a ``using`` parameter you can do it by wrapping the call with
- a ``lambda`` to add an extra parameter::
-
- self.assertNumQueries(7, lambda: my_function(using=7))
-
- You can also use this as a context manager::
-
- with self.assertNumQueries(2):
- Person.objects.create(name="Aaron")
- Person.objects.create(name="Daniel")
-
.. method:: SimpleTestCase.assertHTMLEqual(html1, html2, msg=None)
Asserts that the strings ``html1`` and ``html2`` are equal. The comparison
@@ -1624,6 +1607,8 @@ your test suite.
``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be
raised if one of them cannot be parsed.
+ Output in case of error can be customized with the ``msg`` argument.
+
.. method:: SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None)
Asserts that the strings ``html1`` and ``html2`` are *not* equal. The
@@ -1633,6 +1618,8 @@ your test suite.
``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be
raised if one of them cannot be parsed.
+ Output in case of error can be customized with the ``msg`` argument.
+
.. method:: SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)
.. versionadded:: 1.5
@@ -1644,6 +1631,8 @@ your test suite.
syntax differences. When unvalid XML is passed in any parameter, an
``AssertionError`` is always raised, even if both string are identical.
+ Output in case of error can be customized with the ``msg`` argument.
+
.. method:: SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)
.. versionadded:: 1.5
@@ -1652,6 +1641,68 @@ your test suite.
comparison is based on XML semantics. See
:meth:`~SimpleTestCase.assertXMLEqual` for details.
+ Output in case of error can be customized with the ``msg`` argument.
+
+.. method:: SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')
+
+ .. versionadded:: 1.5
+
+ Asserts that the HTML fragment ``needle`` is contained in the ``haystack`` one.
+
+ If the ``count`` integer argument is specified, then additionally the number
+ of ``needle`` occurrences will be strictly verified.
+
+ Whitespace in most cases is ignored, and attribute ordering is not
+ significant. The passed-in arguments must be valid HTML.
+
+.. method:: SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)
+
+ .. versionadded:: 1.5
+
+ Asserts that the JSON fragments ``raw`` and ``expected_data`` are equal.
+ Usual JSON non-significant whitespace rules apply as the heavyweight is
+ delegated to the :mod:`json` library.
+
+ Output in case of error can be customized with the ``msg`` argument.
+
+.. method:: TransactionTestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True)
+
+ Asserts that a queryset ``qs`` returns a particular list of values ``values``.
+
+ The comparison of the contents of ``qs`` and ``values`` is performed using
+ the function ``transform``; by default, this means that the ``repr()`` of
+ each value is compared. Any other callable can be used if ``repr()`` doesn't
+ provide a unique or helpful comparison.
+
+ By default, the comparison is also ordering dependent. If ``qs`` doesn't
+ provide an implicit ordering, you can set the ``ordered`` parameter to
+ ``False``, which turns the comparison into a Python set comparison.
+
+ .. versionchanged:: 1.6
+
+ The method now checks for undefined order and raises ``ValueError``
+ if undefined order is spotted. The ordering is seen as undefined if
+ the given ``qs`` isn't ordered and the comparison is against more
+ than one ordered values.
+
+.. method:: TransactionTestCase.assertNumQueries(num, func, *args, **kwargs)
+
+ Asserts that when ``func`` is called with ``*args`` and ``**kwargs`` that
+ ``num`` database queries are executed.
+
+ If a ``"using"`` key is present in ``kwargs`` it is used as the database
+ alias for which to check the number of queries. If you wish to call a
+ function with a ``using`` parameter you can do it by wrapping the call with
+ a ``lambda`` to add an extra parameter::
+
+ self.assertNumQueries(7, lambda: my_function(using=7))
+
+ You can also use this as a context manager::
+
+ with self.assertNumQueries(2):
+ Person.objects.create(name="Aaron")
+ Person.objects.create(name="Daniel")
+
.. _topics-testing-email:
Email services
@@ -1701,7 +1752,7 @@ and contents::
self.assertEqual(mail.outbox[0].subject, 'Subject here')
As noted :ref:`previously <emptying-test-outbox>`, the test outbox is emptied
-at the start of every test in a Django ``TestCase``. To empty the outbox
+at the start of every test in a Django ``*TestCase``. To empty the outbox
manually, assign the empty list to ``mail.outbox``::
from django.core import mail
Please sign in to comment.
Something went wrong with that request. Please try again.