From a51c4de1945be2225f20fad794cfb52d8f1f9236 Mon Sep 17 00:00:00 2001
From: Mads Jensen <mje@inducks.org>
Date: Sun, 28 May 2017 21:37:21 +0200
Subject: [PATCH] Used assertRaisesMessage() to test Django's error messages.

---
 tests/admin_registration/tests.py             |  6 +-
 tests/admin_utils/tests.py                    |  2 +-
 tests/aggregation_regress/tests.py            | 34 ++++++++---
 tests/apps/tests.py                           |  6 +-
 tests/auth_tests/test_auth_backends.py        | 12 +++-
 tests/auth_tests/test_basic.py                |  9 ++-
 tests/auth_tests/test_management.py           |  5 +-
 tests/basic/tests.py                          | 20 ++++---
 tests/bulk_create/tests.py                    |  3 +-
 tests/check_framework/tests.py                |  3 +-
 tests/custom_columns/tests.py                 |  6 +-
 tests/custom_lookups/tests.py                 |  8 ++-
 tests/custom_managers/tests.py                |  9 ++-
 tests/custom_pk/tests.py                      |  2 +-
 tests/delete/tests.py                         |  6 +-
 tests/delete_regress/tests.py                 |  5 +-
 tests/distinct_on_fields/tests.py             |  8 ++-
 tests/expressions/tests.py                    |  9 ++-
 tests/flatpages_tests/test_templatetags.py    | 18 +++---
 tests/force_insert_update/tests.py            |  9 ++-
 tests/foreign_object/tests.py                 | 14 ++++-
 .../forms_tests/field_tests/test_uuidfield.py |  3 +-
 tests/forms_tests/tests/test_formsets.py      |  3 +-
 tests/forms_tests/tests/tests.py              |  6 +-
 tests/generic_relations/tests.py              |  7 ++-
 tests/generic_views/test_base.py              |  6 +-
 tests/generic_views/test_dates.py             |  6 +-
 tests/generic_views/test_detail.py            |  6 +-
 tests/generic_views/test_edit.py              | 19 +++---
 tests/generic_views/test_list.py              |  6 +-
 tests/get_earliest_or_latest/tests.py         |  6 +-
 tests/i18n/patterns/tests.py                  |  3 +-
 tests/lookup/tests.py                         | 17 ++++--
 tests/m2m_regress/tests.py                    |  2 +-
 tests/many_to_many/tests.py                   |  6 +-
 tests/many_to_one/tests.py                    | 12 +++-
 tests/migrations/test_commands.py             | 15 ++++-
 tests/migrations/test_operations.py           |  2 +-
 tests/model_fields/test_decimalfield.py       |  3 +-
 tests/model_fields/test_floatfield.py         |  5 +-
 tests/model_forms/tests.py                    | 26 ++++++---
 .../test_abstract_inheritance.py              |  5 +-
 tests/model_inheritance/tests.py              | 12 +++-
 tests/multiple_database/tests.py              | 58 ++++++++++++++-----
 tests/null_queries/tests.py                   |  5 +-
 tests/one_to_one/tests.py                     |  6 +-
 tests/prefetch_related/tests.py               | 24 ++++++--
 tests/queries/tests.py                        |  8 ++-
 tests/redirects_tests/tests.py                |  6 +-
 tests/reverse_lookup/tests.py                 |  6 +-
 tests/select_for_update/tests.py              |  6 +-
 tests/select_related/tests.py                 |  4 --
 tests/settings_tests/tests.py                 |  2 +-
 tests/sitemaps_tests/test_http.py             |  8 ++-
 tests/staticfiles_tests/test_finders.py       |  7 ++-
 tests/syndication_tests/tests.py              |  6 +-
 .../syntax_tests/i18n/test_blocktrans.py      |  8 ++-
 .../syntax_tests/test_filter_syntax.py        | 11 +++-
 .../template_tests/syntax_tests/test_with.py  |  5 +-
 tests/template_tests/test_parser.py           |  6 +-
 tests/test_utils/tests.py                     | 19 ++++--
 tests/timezones/tests.py                      | 12 +++-
 tests/transaction_hooks/tests.py              |  3 +-
 tests/transactions/tests.py                   | 13 +++--
 tests/update/tests.py                         |  3 +-
 tests/update_only_fields/tests.py             | 10 +++-
 tests/urlpatterns_reverse/tests.py            |  7 ++-
 tests/user_commands/tests.py                  |  5 +-
 tests/validators/tests.py                     |  3 +-
 69 files changed, 448 insertions(+), 173 deletions(-)

diff --git a/tests/admin_registration/tests.py b/tests/admin_registration/tests.py
index 125d8eae61..8601328647 100644
--- a/tests/admin_registration/tests.py
+++ b/tests/admin_registration/tests.py
@@ -30,7 +30,8 @@ class TestRegistration(SimpleTestCase):
 
     def test_prevent_double_registration(self):
         self.site.register(Person)
-        with self.assertRaises(admin.sites.AlreadyRegistered):
+        msg = 'The model Person is already registered'
+        with self.assertRaisesMessage(admin.sites.AlreadyRegistered, msg):
             self.site.register(Person)
 
     def test_registration_with_star_star_options(self):
@@ -55,7 +56,8 @@ class TestRegistration(SimpleTestCase):
         Exception is raised when trying to register an abstract model.
         Refs #12004.
         """
-        with self.assertRaises(ImproperlyConfigured):
+        msg = 'The model Location is abstract, so it cannot be registered with admin.'
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             self.site.register(Location)
 
     def test_is_registered_model(self):
diff --git a/tests/admin_utils/tests.py b/tests/admin_utils/tests.py
index 09136ea72a..1f475c2f5b 100644
--- a/tests/admin_utils/tests.py
+++ b/tests/admin_utils/tests.py
@@ -222,7 +222,7 @@ class UtilsTests(SimpleTestCase):
             "article"
         )
 
-        with self.assertRaises(AttributeError):
+        with self.assertRaisesMessage(AttributeError, "Unable to lookup 'unknown' on Article"):
             label_for_field("unknown", Article)
 
         def test_callable(obj):
diff --git a/tests/aggregation_regress/tests.py b/tests/aggregation_regress/tests.py
index 7dc9a95688..91e993a6d1 100644
--- a/tests/aggregation_regress/tests.py
+++ b/tests/aggregation_regress/tests.py
@@ -431,13 +431,23 @@ class AggregationTests(TestCase):
 
     def test_field_error(self):
         # Bad field requests in aggregates are caught and reported
-        with self.assertRaises(FieldError):
+        msg = (
+            "Cannot resolve keyword 'foo' into field. Choices are: authors, "
+            "contact, contact_id, hardbackbook, id, isbn, name, pages, price, "
+            "pubdate, publisher, publisher_id, rating, store, tags"
+        )
+        with self.assertRaisesMessage(FieldError, msg):
             Book.objects.all().aggregate(num_authors=Count('foo'))
 
-        with self.assertRaises(FieldError):
+        with self.assertRaisesMessage(FieldError, msg):
             Book.objects.all().annotate(num_authors=Count('foo'))
 
-        with self.assertRaises(FieldError):
+        msg = (
+            "Cannot resolve keyword 'foo' into field. Choices are: authors, "
+            "contact, contact_id, hardbackbook, id, isbn, name, num_authors, "
+            "pages, price, pubdate, publisher, publisher_id, rating, store, tags"
+        )
+        with self.assertRaisesMessage(FieldError, msg):
             Book.objects.all().annotate(num_authors=Count('authors__id')).aggregate(Max('foo'))
 
     def test_more(self):
@@ -738,19 +748,25 @@ class AggregationTests(TestCase):
 
     def test_duplicate_alias(self):
         # Regression for #11256 - duplicating a default alias raises ValueError.
-        with self.assertRaises(ValueError):
+        msg = (
+            "The named annotation 'authors__age__avg' conflicts with "
+            "the default name for another annotation."
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             Book.objects.all().annotate(Avg('authors__age'), authors__age__avg=Avg('authors__age'))
 
     def test_field_name_conflict(self):
         # Regression for #11256 - providing an aggregate name
         # that conflicts with a field name on the model raises ValueError
-        with self.assertRaises(ValueError):
+        msg = "The annotation 'age' conflicts with a field on the model."
+        with self.assertRaisesMessage(ValueError, msg):
             Author.objects.annotate(age=Avg('friends__age'))
 
     def test_m2m_name_conflict(self):
         # Regression for #11256 - providing an aggregate name
         # that conflicts with an m2m name on the model raises ValueError
-        with self.assertRaises(ValueError):
+        msg = "The annotation 'friends' conflicts with a field on the model."
+        with self.assertRaisesMessage(ValueError, msg):
             Author.objects.annotate(friends=Count('friends'))
 
     def test_values_queryset_non_conflict(self):
@@ -778,7 +794,8 @@ class AggregationTests(TestCase):
     def test_reverse_relation_name_conflict(self):
         # Regression for #11256 - providing an aggregate name
         # that conflicts with a reverse-related name on the model raises ValueError
-        with self.assertRaises(ValueError):
+        msg = "The annotation 'book_contact_set' conflicts with a field on the model."
+        with self.assertRaisesMessage(ValueError, msg):
             Author.objects.annotate(book_contact_set=Avg('friends__age'))
 
     def test_pickle(self):
@@ -937,7 +954,8 @@ class AggregationTests(TestCase):
 
         # Regression for #10766 - Shouldn't be able to reference an aggregate
         # fields in an aggregate() call.
-        with self.assertRaises(FieldError):
+        msg = "Cannot compute Avg('mean_age'): 'mean_age' is an aggregate"
+        with self.assertRaisesMessage(FieldError, msg):
             Book.objects.annotate(mean_age=Avg('authors__age')).annotate(Avg('mean_age'))
 
     def test_empty_filter_count(self):
diff --git a/tests/apps/tests.py b/tests/apps/tests.py
index 50b3927434..2910220b94 100644
--- a/tests/apps/tests.py
+++ b/tests/apps/tests.py
@@ -53,7 +53,8 @@ class AppsTests(SimpleTestCase):
         """
         Tests when INSTALLED_APPS contains an incorrect app config.
         """
-        with self.assertRaises(ImproperlyConfigured):
+        msg = "'apps.apps.BadConfig' must supply a name attribute."
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             with self.settings(INSTALLED_APPS=['apps.apps.BadConfig']):
                 pass
 
@@ -61,7 +62,8 @@ class AppsTests(SimpleTestCase):
         """
         Tests when INSTALLED_APPS contains a class that isn't an app config.
         """
-        with self.assertRaises(ImproperlyConfigured):
+        msg = "'apps.apps.NotAConfig' isn't a subclass of AppConfig."
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             with self.settings(INSTALLED_APPS=['apps.apps.NotAConfig']):
                 pass
 
diff --git a/tests/auth_tests/test_auth_backends.py b/tests/auth_tests/test_auth_backends.py
index ea9ddd426c..744f8ad817 100644
--- a/tests/auth_tests/test_auth_backends.py
+++ b/tests/auth_tests/test_auth_backends.py
@@ -445,7 +445,11 @@ class NoBackendsTest(TestCase):
         self.user = User.objects.create_user('test', 'test@example.com', 'test')
 
     def test_raises_exception(self):
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            'No authentication backends have been defined. '
+            'Does AUTHENTICATION_BACKENDS contain anything?'
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             self.user.has_perm(('perm', TestObj()))
 
 
@@ -626,7 +630,11 @@ class ImproperlyConfiguredUserModelTest(TestCase):
         request = HttpRequest()
         request.session = self.client.session
 
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            "AUTH_USER_MODEL refers to model 'thismodel.doesntexist' "
+            "that has not been installed"
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             get_user(request)
 
 
diff --git a/tests/auth_tests/test_basic.py b/tests/auth_tests/test_basic.py
index b2c70faffb..80a56f8ca3 100644
--- a/tests/auth_tests/test_basic.py
+++ b/tests/auth_tests/test_basic.py
@@ -97,13 +97,18 @@ class BasicTestCase(TestCase):
     @override_settings(AUTH_USER_MODEL='badsetting')
     def test_swappable_user_bad_setting(self):
         "The alternate user setting must point to something in the format app.model"
-        with self.assertRaises(ImproperlyConfigured):
+        msg = "AUTH_USER_MODEL must be of the form 'app_label.model_name'"
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             get_user_model()
 
     @override_settings(AUTH_USER_MODEL='thismodel.doesntexist')
     def test_swappable_user_nonexistent_model(self):
         "The current user model must point to an installed model"
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            "AUTH_USER_MODEL refers to model 'thismodel.doesntexist' "
+            "that has not been installed"
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             get_user_model()
 
     def test_user_verbose_names_translatable(self):
diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py
index c4368b3bcf..c43091d932 100644
--- a/tests/auth_tests/test_management.py
+++ b/tests/auth_tests/test_management.py
@@ -158,7 +158,8 @@ class ChangepasswordManagementCommandTestCase(TestCase):
         A CommandError should be thrown by handle() if the user enters in
         mismatched passwords three times.
         """
-        with self.assertRaises(CommandError):
+        msg = "Aborting password change for user 'joe' after 3 attempts"
+        with self.assertRaisesMessage(CommandError, msg):
             call_command('changepassword', username='joe', stdout=self.stdout, stderr=self.stderr)
 
     @mock.patch.object(changepassword.Command, '_get_pass', return_value='1234567890')
@@ -316,7 +317,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
         # We skip validation because the temporary substitution of the
         # swappable User model messes with validation.
         new_io = StringIO()
-        with self.assertRaises(CommandError):
+        with self.assertRaisesMessage(CommandError, 'You must use --email with --noinput.'):
             call_command(
                 "createsuperuser",
                 interactive=False,
diff --git a/tests/basic/tests.py b/tests/basic/tests.py
index d55277c0f2..c44e17dd24 100644
--- a/tests/basic/tests.py
+++ b/tests/basic/tests.py
@@ -335,8 +335,8 @@ class ModelTest(TestCase):
         self.assertEqual(article.headline, notlazy)
 
     def test_emptyqs(self):
-        # Can't be instantiated
-        with self.assertRaises(TypeError):
+        msg = "EmptyQuerySet can't be instantiated"
+        with self.assertRaisesMessage(TypeError, msg):
             EmptyQuerySet()
         self.assertIsInstance(Article.objects.none(), EmptyQuerySet)
         self.assertNotIsInstance('', EmptyQuerySet)
@@ -397,7 +397,8 @@ class ModelTest(TestCase):
     def test_hash(self):
         # Value based on PK
         self.assertEqual(hash(Article(id=1)), hash(1))
-        with self.assertRaises(TypeError):
+        msg = 'Model instances without primary key value are unhashable'
+        with self.assertRaisesMessage(TypeError, msg):
             # No PK value -> unhashable (because save() would then change
             # hash)
             hash(Article())
@@ -618,7 +619,7 @@ class SelectOnSaveTests(TestCase):
         with self.assertNumQueries(1):
             asos.save(force_update=True)
         Article.objects.all().delete()
-        with self.assertRaises(DatabaseError):
+        with self.assertRaisesMessage(DatabaseError, 'Forced update did not affect any rows.'):
             with self.assertNumQueries(1):
                 asos.save(force_update=True)
 
@@ -653,9 +654,13 @@ class SelectOnSaveTests(TestCase):
             # This is not wanted behavior, but this is how Django has always
             # behaved for databases that do not return correct information
             # about matched rows for UPDATE.
-            with self.assertRaises(DatabaseError):
+            with self.assertRaisesMessage(DatabaseError, 'Forced update did not affect any rows.'):
                 asos.save(force_update=True)
-            with self.assertRaises(DatabaseError):
+            msg = (
+                "An error occurred in the current transaction. You can't "
+                "execute queries until the end of the 'atomic' block."
+            )
+            with self.assertRaisesMessage(DatabaseError, msg):
                 asos.save(update_fields=['pub_date'])
         finally:
             Article._base_manager._queryset_class = orig_class
@@ -688,7 +693,8 @@ class ModelRefreshTests(TestCase):
 
     def test_unknown_kwarg(self):
         s = SelfRef.objects.create()
-        with self.assertRaises(TypeError):
+        msg = "refresh_from_db() got an unexpected keyword argument 'unknown_kwarg'"
+        with self.assertRaisesMessage(TypeError, msg):
             s.refresh_from_db(unknown_kwarg=10)
 
     def test_refresh_fk(self):
diff --git a/tests/bulk_create/tests.py b/tests/bulk_create/tests.py
index 6b3e0bedc4..2439050623 100644
--- a/tests/bulk_create/tests.py
+++ b/tests/bulk_create/tests.py
@@ -108,7 +108,8 @@ class BulkCreateTests(TestCase):
         """
         valid_country = Country(name='Germany', iso_two_letter='DE')
         invalid_country = Country(id=0, name='Poland', iso_two_letter='PL')
-        with self.assertRaises(ValueError):
+        msg = 'The database backend does not accept 0 as a value for AutoField.'
+        with self.assertRaisesMessage(ValueError, msg):
             Country.objects.bulk_create([valid_country, invalid_country])
 
     def test_batch_same_vals(self):
diff --git a/tests/check_framework/tests.py b/tests/check_framework/tests.py
index 2128da3dc6..abb4298c65 100644
--- a/tests/check_framework/tests.py
+++ b/tests/check_framework/tests.py
@@ -179,7 +179,8 @@ class CheckCommandTests(SimpleTestCase):
 
     @override_system_checks([simple_system_check, tagged_system_check])
     def test_invalid_tag(self):
-        with self.assertRaises(CommandError):
+        msg = 'There is no system check with the "missingtag" tag.'
+        with self.assertRaisesMessage(CommandError, msg):
             call_command('check', tags=['missingtag'])
 
     @override_system_checks([simple_system_check])
diff --git a/tests/custom_columns/tests.py b/tests/custom_columns/tests.py
index 2d7044b8de..f5dfd9c0cd 100644
--- a/tests/custom_columns/tests.py
+++ b/tests/custom_columns/tests.py
@@ -37,7 +37,11 @@ class CustomColumnsTests(TestCase):
         )
 
     def test_field_error(self):
-        with self.assertRaises(FieldError):
+        msg = (
+            "Cannot resolve keyword 'firstname' into field. Choices are: "
+            "Author_ID, article, first_name, last_name, primary_set"
+        )
+        with self.assertRaisesMessage(FieldError, msg):
             Author.objects.filter(firstname__exact="John")
 
     def test_attribute_error(self):
diff --git a/tests/custom_lookups/tests.py b/tests/custom_lookups/tests.py
index 4a477b4687..d39ebe6cdc 100644
--- a/tests/custom_lookups/tests.py
+++ b/tests/custom_lookups/tests.py
@@ -319,7 +319,8 @@ class BilateralTransformTests(TestCase):
 
     def test_bilateral_inner_qs(self):
         with register_lookup(models.CharField, UpperBilateralTransform):
-            with self.assertRaises(NotImplementedError):
+            msg = 'Bilateral transformations on nested querysets are not supported.'
+            with self.assertRaisesMessage(NotImplementedError, msg):
                 Author.objects.filter(name__upper__in=Author.objects.values_list('name'))
 
     def test_bilateral_multi_value(self):
@@ -501,13 +502,14 @@ class LookupTransformCallOrderTests(TestCase):
     def test_call_order(self):
         with register_lookup(models.DateField, TrackCallsYearTransform):
             # junk lookup - tries lookup, then transform, then fails
-            with self.assertRaises(FieldError):
+            msg = "Unsupported lookup 'junk' for IntegerField or join on the field not permitted."
+            with self.assertRaisesMessage(FieldError, msg):
                 Author.objects.filter(birthdate__testyear__junk=2012)
             self.assertEqual(TrackCallsYearTransform.call_order,
                              ['lookup', 'transform'])
             TrackCallsYearTransform.call_order = []
             # junk transform - tries transform only, then fails
-            with self.assertRaises(FieldError):
+            with self.assertRaisesMessage(FieldError, msg):
                 Author.objects.filter(birthdate__testyear__junk__more_junk=2012)
             self.assertEqual(TrackCallsYearTransform.call_order,
                              ['transform'])
diff --git a/tests/custom_managers/tests.py b/tests/custom_managers/tests.py
index d095036f10..ee2ac1d552 100644
--- a/tests/custom_managers/tests.py
+++ b/tests/custom_managers/tests.py
@@ -58,7 +58,8 @@ class CustomManagerTests(TestCase):
                 # Methods with queryset_only=False are copied even if they are private.
                 manager._optin_private_method()
                 # Methods with queryset_only=True aren't copied even if they are public.
-                with self.assertRaises(AttributeError):
+                msg = "%r object has no attribute 'optout_public_method'" % manager.__class__.__name__
+                with self.assertRaisesMessage(AttributeError, msg):
                     manager.optout_public_method()
 
     def test_manager_use_queryset_methods(self):
@@ -93,7 +94,8 @@ class CustomManagerTests(TestCase):
         querysets.
         """
         Person.custom_queryset_custom_manager.manager_only()
-        with self.assertRaises(AttributeError):
+        msg = "'CustomQuerySet' object has no attribute 'manager_only'"
+        with self.assertRaisesMessage(AttributeError, msg):
             Person.custom_queryset_custom_manager.all().manager_only()
 
     def test_queryset_and_manager(self):
@@ -116,7 +118,8 @@ class CustomManagerTests(TestCase):
         The default manager, "objects", doesn't exist, because a custom one
         was provided.
         """
-        with self.assertRaises(AttributeError):
+        msg = "type object 'Book' has no attribute 'objects'"
+        with self.assertRaisesMessage(AttributeError, msg):
             Book.objects
 
     def test_filtering(self):
diff --git a/tests/custom_pk/tests.py b/tests/custom_pk/tests.py
index 7c89b6d120..da0cff14cc 100644
--- a/tests/custom_pk/tests.py
+++ b/tests/custom_pk/tests.py
@@ -145,7 +145,7 @@ class BasicCustomPKTests(TestCase):
         # Or we can use the real attribute name for the primary key:
         self.assertEqual(e.employee_code, 123)
 
-        with self.assertRaises(AttributeError):
+        with self.assertRaisesMessage(AttributeError, "'Employee' object has no attribute 'id'"):
             e.id
 
     def test_in_bulk(self):
diff --git a/tests/delete/tests.py b/tests/delete/tests.py
index 0c7e218d38..98467efb6a 100644
--- a/tests/delete/tests.py
+++ b/tests/delete/tests.py
@@ -60,7 +60,11 @@ class OnDeleteTests(TestCase):
 
     def test_protect(self):
         a = create_a('protect')
-        with self.assertRaises(IntegrityError):
+        msg = (
+            "Cannot delete some instances of model 'R' because they are "
+            "referenced through a protected foreign key: 'A.protect'"
+        )
+        with self.assertRaisesMessage(IntegrityError, msg):
             a.protect.delete()
 
     def test_do_nothing(self):
diff --git a/tests/delete_regress/tests.py b/tests/delete_regress/tests.py
index 7472731e94..7f913257ab 100644
--- a/tests/delete_regress/tests.py
+++ b/tests/delete_regress/tests.py
@@ -244,9 +244,10 @@ class ProxyDeleteTest(TestCase):
         self.assertEqual(len(FooFileProxy.objects.all()), 0)
 
     def test_19187_values(self):
-        with self.assertRaises(TypeError):
+        msg = 'Cannot call delete() after .values() or .values_list()'
+        with self.assertRaisesMessage(TypeError, msg):
             Image.objects.values().delete()
-        with self.assertRaises(TypeError):
+        with self.assertRaisesMessage(TypeError, msg):
             Image.objects.values_list().delete()
 
 
diff --git a/tests/distinct_on_fields/tests.py b/tests/distinct_on_fields/tests.py
index 6bb518d2b1..93a332cf83 100644
--- a/tests/distinct_on_fields/tests.py
+++ b/tests/distinct_on_fields/tests.py
@@ -97,16 +97,18 @@ class DistinctOnTests(TestCase):
 
     def test_distinct_not_implemented_checks(self):
         # distinct + annotate not allowed
-        with self.assertRaises(NotImplementedError):
+        msg = 'annotate() + distinct(fields) is not implemented.'
+        with self.assertRaisesMessage(NotImplementedError, msg):
             Celebrity.objects.annotate(Max('id')).distinct('id')[0]
-        with self.assertRaises(NotImplementedError):
+        with self.assertRaisesMessage(NotImplementedError, msg):
             Celebrity.objects.distinct('id').annotate(Max('id'))[0]
 
         # However this check is done only when the query executes, so you
         # can use distinct() to remove the fields before execution.
         Celebrity.objects.distinct('id').annotate(Max('id')).distinct()[0]
         # distinct + aggregate not allowed
-        with self.assertRaises(NotImplementedError):
+        msg = 'aggregate() + distinct(fields) not implemented.'
+        with self.assertRaisesMessage(NotImplementedError, msg):
             Celebrity.objects.distinct('id').aggregate(Max('id'))
 
     def test_distinct_on_in_ordered_subquery(self):
diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py
index aa090d2746..8952045002 100644
--- a/tests/expressions/tests.py
+++ b/tests/expressions/tests.py
@@ -246,7 +246,8 @@ class BasicExpressionsTests(TestCase):
         )
 
         with transaction.atomic():
-            with self.assertRaises(FieldError):
+            msg = "Joined field references are not permitted in this query"
+            with self.assertRaisesMessage(FieldError, msg):
                 Company.objects.exclude(
                     ceo__firstname=F('point_of_contact__firstname')
                 ).update(name=F('point_of_contact__lastname'))
@@ -293,13 +294,15 @@ class BasicExpressionsTests(TestCase):
 
         def test():
             test_gmbh.point_of_contact = F("ceo")
-        with self.assertRaises(ValueError):
+        msg = 'F(ceo)": "Company.point_of_contact" must be a "Employee" instance.'
+        with self.assertRaisesMessage(ValueError, msg):
             test()
 
         test_gmbh.point_of_contact = test_gmbh.ceo
         test_gmbh.save()
         test_gmbh.name = F("ceo__last_name")
-        with self.assertRaises(FieldError):
+        msg = 'Joined field references are not permitted in this query'
+        with self.assertRaisesMessage(FieldError, msg):
             test_gmbh.save()
 
     def test_object_update_unsaved_objects(self):
diff --git a/tests/flatpages_tests/test_templatetags.py b/tests/flatpages_tests/test_templatetags.py
index 688d85a224..1ce8f65079 100644
--- a/tests/flatpages_tests/test_templatetags.py
+++ b/tests/flatpages_tests/test_templatetags.py
@@ -128,17 +128,21 @@ class FlatpageTemplateTagTests(TestCase):
         def render(t):
             return Template(t).render(Context())
 
-        with self.assertRaises(TemplateSyntaxError):
+        msg = (
+            "get_flatpages expects a syntax of get_flatpages "
+            "['url_starts_with'] [for user] as context_name"
+        )
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             render("{% load flatpages %}{% get_flatpages %}")
-        with self.assertRaises(TemplateSyntaxError):
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             render("{% load flatpages %}{% get_flatpages as %}")
-        with self.assertRaises(TemplateSyntaxError):
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             render("{% load flatpages %}{% get_flatpages cheesecake flatpages %}")
-        with self.assertRaises(TemplateSyntaxError):
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             render("{% load flatpages %}{% get_flatpages as flatpages asdf %}")
-        with self.assertRaises(TemplateSyntaxError):
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             render("{% load flatpages %}{% get_flatpages cheesecake user as flatpages %}")
-        with self.assertRaises(TemplateSyntaxError):
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             render("{% load flatpages %}{% get_flatpages for user as flatpages asdf %}")
-        with self.assertRaises(TemplateSyntaxError):
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             render("{% load flatpages %}{% get_flatpages prefix for user as flatpages asdf %}")
diff --git a/tests/force_insert_update/tests.py b/tests/force_insert_update/tests.py
index 2232283cb8..8ffe98ad4f 100644
--- a/tests/force_insert_update/tests.py
+++ b/tests/force_insert_update/tests.py
@@ -20,13 +20,15 @@ class ForceTests(TestCase):
         # Won't work because force_update and force_insert are mutually
         # exclusive
         c.value = 4
-        with self.assertRaises(ValueError):
+        msg = 'Cannot force both insert and updating in model saving.'
+        with self.assertRaisesMessage(ValueError, msg):
             c.save(force_insert=True, force_update=True)
 
         # Try to update something that doesn't have a primary key in the first
         # place.
         c1 = Counter(name="two", value=2)
-        with self.assertRaises(ValueError):
+        msg = 'Cannot force an update in save() with no primary key.'
+        with self.assertRaisesMessage(ValueError, msg):
             with transaction.atomic():
                 c1.save(force_update=True)
         c1.save(force_insert=True)
@@ -40,7 +42,8 @@ class ForceTests(TestCase):
         # Trying to update should still fail, even with manual primary keys, if
         # the data isn't in the database already.
         obj = WithCustomPK(name=1, value=1)
-        with self.assertRaises(DatabaseError):
+        msg = 'Forced update did not affect any rows.'
+        with self.assertRaisesMessage(DatabaseError, msg):
             with transaction.atomic():
                 obj.save(force_update=True)
 
diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py
index e74732ab72..59d4357802 100644
--- a/tests/foreign_object/tests.py
+++ b/tests/foreign_object/tests.py
@@ -368,7 +368,12 @@ class MultiColumnFKTests(TestCase):
         ArticleTag.objects.create(article=a1, name="foo")
         self.assertEqual(Article.objects.filter(tag__name="foo").count(), 1)
         self.assertEqual(Article.objects.filter(tag__name="bar").count(), 0)
-        with self.assertRaises(FieldError):
+        msg = (
+            "Cannot resolve keyword 'tags' into field. Choices are: "
+            "active_translation, active_translation_q, articletranslation, "
+            "id, idea_things, newsarticle, pub_date, tag"
+        )
+        with self.assertRaisesMessage(FieldError, msg):
             Article.objects.filter(tags__name="foo")
 
     def test_many_to_many_related_query_name(self):
@@ -377,7 +382,12 @@ class MultiColumnFKTests(TestCase):
         a1.ideas.add(i1)
         self.assertEqual(Article.objects.filter(idea_things__name="idea1").count(), 1)
         self.assertEqual(Article.objects.filter(idea_things__name="idea2").count(), 0)
-        with self.assertRaises(FieldError):
+        msg = (
+            "Cannot resolve keyword 'ideas' into field. Choices are: "
+            "active_translation, active_translation_q, articletranslation, "
+            "id, idea_things, newsarticle, pub_date, tag"
+        )
+        with self.assertRaisesMessage(FieldError, msg):
             Article.objects.filter(ideas__name="idea1")
 
     @translation.override('fi')
diff --git a/tests/forms_tests/field_tests/test_uuidfield.py b/tests/forms_tests/field_tests/test_uuidfield.py
index 08498ab9c9..ed08efd659 100644
--- a/tests/forms_tests/field_tests/test_uuidfield.py
+++ b/tests/forms_tests/field_tests/test_uuidfield.py
@@ -18,9 +18,8 @@ class UUIDFieldTest(SimpleTestCase):
 
     def test_uuidfield_3(self):
         field = UUIDField()
-        with self.assertRaises(ValidationError) as cm:
+        with self.assertRaisesMessage(ValidationError, 'Enter a valid UUID.'):
             field.clean('550e8400')
-        self.assertEqual(cm.exception.messages[0], 'Enter a valid UUID.')
 
     def test_uuidfield_4(self):
         field = UUIDField()
diff --git a/tests/forms_tests/tests/test_formsets.py b/tests/forms_tests/tests/test_formsets.py
index 29f138b2d1..defa46b730 100644
--- a/tests/forms_tests/tests/test_formsets.py
+++ b/tests/forms_tests/tests/test_formsets.py
@@ -1338,7 +1338,8 @@ ArticleFormSet = formset_factory(ArticleForm)
 
 class TestIsBoundBehavior(SimpleTestCase):
     def test_no_data_raises_validation_error(self):
-        with self.assertRaises(ValidationError):
+        msg = 'ManagementForm data is missing or has been tampered with'
+        with self.assertRaisesMessage(ValidationError, msg):
             ArticleFormSet({}).is_valid()
 
     def test_with_management_data_attrs_work_fine(self):
diff --git a/tests/forms_tests/tests/tests.py b/tests/forms_tests/tests/tests.py
index 043b8e0edc..a54ad09c26 100644
--- a/tests/forms_tests/tests/tests.py
+++ b/tests/forms_tests/tests/tests.py
@@ -247,7 +247,11 @@ class RelatedModelFormTests(SimpleTestCase):
             model = A
             fields = '__all__'
 
-        with self.assertRaises(ValueError):
+        msg = (
+            "Cannot create form field for 'ref' yet, because "
+            "its related model 'B' has not been loaded yet"
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             ModelFormMetaclass('Form', (ModelForm,), {'Meta': Meta})
 
         class B(models.Model):
diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py
index 91bb616420..d3aa3b15f8 100644
--- a/tests/generic_relations/tests.py
+++ b/tests/generic_relations/tests.py
@@ -347,7 +347,12 @@ class GenericRelationsTests(TestCase):
 
     def test_generic_relation_related_name_default(self):
         # GenericRelation isn't usable from the reverse side by default.
-        with self.assertRaises(FieldError):
+        msg = (
+            "Cannot resolve keyword 'vegetable' into field. Choices are: "
+            "animal, content_object, content_type, content_type_id, id, "
+            "manualpk, object_id, tag, valuabletaggeditem"
+        )
+        with self.assertRaisesMessage(FieldError, msg):
             TaggedItem.objects.filter(vegetable__isnull=True)
 
     def test_multiple_gfk(self):
diff --git a/tests/generic_views/test_base.py b/tests/generic_views/test_base.py
index 8215e67e1a..1579408712 100644
--- a/tests/generic_views/test_base.py
+++ b/tests/generic_views/test_base.py
@@ -273,7 +273,11 @@ class TemplateViewTest(SimpleTestCase):
         """
         A template view must provide a template name.
         """
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            "TemplateResponseMixin requires either a definition of "
+            "'template_name' or an implementation of 'get_template_names()'"
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             self.client.get('/template/no_template/')
 
     @require_jinja2
diff --git a/tests/generic_views/test_dates.py b/tests/generic_views/test_dates.py
index 454b9e5a1d..ff3b09ddf3 100644
--- a/tests/generic_views/test_dates.py
+++ b/tests/generic_views/test_dates.py
@@ -79,7 +79,11 @@ class ArchiveIndexViewTests(TestDataMixin, TestCase):
         self.assertTemplateUsed(res, 'generic_views/book_detail.html')
 
     def test_archive_view_invalid(self):
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            'BookArchive is missing a QuerySet. Define BookArchive.model, '
+            'BookArchive.queryset, or override BookArchive.get_queryset().'
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             self.client.get('/dates/books/invalid/')
 
     def test_archive_view_by_month(self):
diff --git a/tests/generic_views/test_detail.py b/tests/generic_views/test_detail.py
index da20db066e..002435dfce 100644
--- a/tests/generic_views/test_detail.py
+++ b/tests/generic_views/test_detail.py
@@ -175,7 +175,11 @@ class DetailViewTest(TestCase):
             self.client.get('/detail/author/invalid/url/')
 
     def test_invalid_queryset(self):
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            'AuthorDetail is missing a QuerySet. Define AuthorDetail.model, '
+            'AuthorDetail.queryset, or override AuthorDetail.get_queryset().'
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             self.client.get('/detail/author/invalid/qs/')
 
     def test_non_model_object_with_meta(self):
diff --git a/tests/generic_views/test_edit.py b/tests/generic_views/test_edit.py
index 3a68fc4098..522189a475 100644
--- a/tests/generic_views/test_edit.py
+++ b/tests/generic_views/test_edit.py
@@ -159,7 +159,11 @@ class CreateViewTests(TestCase):
         self.assertQuerysetEqual(Author.objects.all(), ['<Author: Randall Munroe>'])
 
     def test_create_without_redirect(self):
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            'No URL to redirect to.  Either provide a url or define a '
+            'get_absolute_url method on the Model.'
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             self.client.post('/edit/authors/create/naive/', {'name': 'Randall Munroe', 'slug': 'randall-munroe'})
 
     def test_create_restricted(self):
@@ -312,9 +316,11 @@ class UpdateViewTests(TestCase):
             name='Randall Munroe',
             slug='randall-munroe',
         )
-        # Should raise exception -- No redirect URL provided, and no
-        # get_absolute_url provided
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            'No URL to redirect to.  Either provide a url or define a '
+            'get_absolute_url method on the Model.'
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             self.client.post(
                 '/edit/author/%d/update/naive/' % a.pk,
                 {'name': 'Randall Munroe (author of xkcd)', 'slug': 'randall-munroe'}
@@ -404,7 +410,6 @@ class DeleteViewTests(TestCase):
             name='Randall Munroe',
             slug='randall-munroe',
         )
-        # Should raise exception -- No redirect URL provided, and no
-        # get_absolute_url provided
-        with self.assertRaises(ImproperlyConfigured):
+        msg = 'No URL to redirect to. Provide a success_url.'
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             self.client.post('/edit/author/%d/delete/naive/' % a.pk)
diff --git a/tests/generic_views/test_list.py b/tests/generic_views/test_list.py
index 429d46f50c..ea57b6af9a 100644
--- a/tests/generic_views/test_list.py
+++ b/tests/generic_views/test_list.py
@@ -200,7 +200,11 @@ class ListViewTests(TestCase):
         self.assertTemplateUsed(res, 'generic_views/author_list.html')
 
     def test_missing_items(self):
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            'AuthorList is missing a QuerySet. Define AuthorList.model, '
+            'AuthorList.queryset, or override AuthorList.get_queryset().'
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             self.client.get('/list/authors/invalid/')
 
     def test_paginated_list_view_does_not_load_entire_table(self):
diff --git a/tests/get_earliest_or_latest/tests.py b/tests/get_earliest_or_latest/tests.py
index 2322e0a91c..c59f4324f0 100644
--- a/tests/get_earliest_or_latest/tests.py
+++ b/tests/get_earliest_or_latest/tests.py
@@ -118,7 +118,11 @@ class EarliestOrLatestTests(TestCase):
         # "get_latest_by" set -- just pass in the field name manually.
         Person.objects.create(name="Ralph", birthday=datetime(1950, 1, 1))
         p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3))
-        with self.assertRaises(AssertionError):
+        msg = (
+            "earliest() and latest() require either a field_name parameter or "
+            "'get_latest_by' in the model"
+        )
+        with self.assertRaisesMessage(AssertionError, msg):
             Person.objects.latest()
         self.assertEqual(Person.objects.latest("birthday"), p2)
 
diff --git a/tests/i18n/patterns/tests.py b/tests/i18n/patterns/tests.py
index a1d4269735..0f69f0d8c5 100644
--- a/tests/i18n/patterns/tests.py
+++ b/tests/i18n/patterns/tests.py
@@ -78,7 +78,8 @@ class URLPrefixTests(URLTestCaseBase):
 
     @override_settings(ROOT_URLCONF='i18n.patterns.urls.wrong')
     def test_invalid_prefix_use(self):
-        with self.assertRaises(ImproperlyConfigured):
+        msg = 'Using i18n_patterns in an included URLconf is not allowed.'
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             reverse('account:register')
 
 
diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py
index 289d8dde1b..41270cd19f 100644
--- a/tests/lookup/tests.py
+++ b/tests/lookup/tests.py
@@ -315,7 +315,11 @@ class LookupTests(TestCase):
         # However, an exception FieldDoesNotExist will be thrown if you specify
         # a nonexistent field name in values() (a field that is neither in the
         # model nor in extra(select)).
-        with self.assertRaises(FieldError):
+        msg = (
+            "Cannot resolve keyword 'id_plus_two' into field. Choices are: "
+            "author, author_id, headline, id, id_plus_one, pub_date, slug, tag"
+        )
+        with self.assertRaisesMessage(FieldError, msg):
             Article.objects.extra(select={'id_plus_one': 'id + 1'}).values('id', 'id_plus_two')
         # If you don't specify field names to values(), all are returned.
         self.assertSequenceEqual(
@@ -733,11 +737,16 @@ class LookupTests(TestCase):
         """
         A lookup query containing non-fields raises the proper exception.
         """
-        with self.assertRaises(FieldError):
+        msg = "Unsupported lookup 'blahblah' for CharField or join on the field not permitted."
+        with self.assertRaisesMessage(FieldError, msg):
             Article.objects.filter(headline__blahblah=99)
-        with self.assertRaises(FieldError):
+        with self.assertRaisesMessage(FieldError, msg):
             Article.objects.filter(headline__blahblah__exact=99)
-        with self.assertRaises(FieldError):
+        msg = (
+            "Cannot resolve keyword 'blahblah' into field. Choices are: "
+            "author, author_id, headline, id, pub_date, slug, tag"
+        )
+        with self.assertRaisesMessage(FieldError, msg):
             Article.objects.filter(blahblah=99)
 
     def test_lookup_collision(self):
diff --git a/tests/m2m_regress/tests.py b/tests/m2m_regress/tests.py
index 6d4a4f02ed..b38ccf83be 100644
--- a/tests/m2m_regress/tests.py
+++ b/tests/m2m_regress/tests.py
@@ -102,7 +102,7 @@ class M2MRegressionTests(TestCase):
         c1 = TagCollection.objects.create(name='c1')
         c1.tags.set([t1, t2])
 
-        with self.assertRaises(TypeError):
+        with self.assertRaisesMessage(TypeError, "'int' object is not iterable"):
             c1.tags.set(7)
 
         c1.refresh_from_db()
diff --git a/tests/many_to_many/tests.py b/tests/many_to_many/tests.py
index d81d88b176..5b1c6e1516 100644
--- a/tests/many_to_many/tests.py
+++ b/tests/many_to_many/tests.py
@@ -29,7 +29,11 @@ class ManyToManyTests(TestCase):
         # Create an Article.
         a5 = Article(headline='Django lets you reate Web apps easily')
         # You can't associate it with a Publication until it's been saved.
-        with self.assertRaises(ValueError):
+        msg = (
+            '"<Article: Django lets you reate Web apps easily>" needs to have '
+            'a value for field "id" before this many-to-many relationship can be used.'
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             getattr(a5, 'publications')
         # Save it!
         a5.save()
diff --git a/tests/many_to_one/tests.py b/tests/many_to_one/tests.py
index 0382a9a45e..aab050234c 100644
--- a/tests/many_to_one/tests.py
+++ b/tests/many_to_one/tests.py
@@ -406,7 +406,8 @@ class ManyToOneTests(TestCase):
         self.assertEqual(a3.reporter.id, self.r2.id)
 
         # Get should respect explicit foreign keys as well.
-        with self.assertRaises(MultipleObjectsReturned):
+        msg = 'get() returned more than one Article -- it returned 2!'
+        with self.assertRaisesMessage(MultipleObjectsReturned, msg):
             Article.objects.get(reporter_id=self.r.id)
         self.assertEqual(
             repr(a3),
@@ -484,7 +485,11 @@ class ManyToOneTests(TestCase):
         setattr(c, "parent", None)
 
         # You also can't assign an object of the wrong type here
-        with self.assertRaises(ValueError):
+        msg = (
+            'Cannot assign "<First: First object (1)>": "Child.parent" must '
+            'be a "Parent" instance.'
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             setattr(c, "parent", First(id=1, second=1))
 
         # You can assign None to Child.parent during object creation.
@@ -550,7 +555,8 @@ class ManyToOneTests(TestCase):
 
         p = Parent.objects.create(name="Parent")
         c = Child.objects.create(name="Child", parent=p)
-        with self.assertRaises(ValueError):
+        msg = 'Cannot assign "%r": "Child.parent" must be a "Parent" instance.' % c
+        with self.assertRaisesMessage(ValueError, msg):
             Child.objects.create(name="Grandchild", parent=c)
 
     def test_fk_instantiation_outside_model(self):
diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py
index 3e4f7b5410..e6aaa8fc90 100644
--- a/tests/migrations/test_commands.py
+++ b/tests/migrations/test_commands.py
@@ -757,8 +757,18 @@ class MakeMigrationsTests(MigrationTestBase):
         makemigrations exits if it detects a conflict.
         """
         with self.temporary_migration_module(module="migrations.test_migrations_conflict"):
-            with self.assertRaises(CommandError):
+            with self.assertRaises(CommandError) as context:
                 call_command("makemigrations")
+        exception_message = str(context.exception)
+        self.assertIn(
+            'Conflicting migrations detected; multiple leaf nodes '
+            'in the migration graph:',
+            exception_message
+        )
+        self.assertIn('0002_second', exception_message)
+        self.assertIn('0002_conflicting_second', exception_message)
+        self.assertIn('in migrations', exception_message)
+        self.assertIn("To fix them run 'python manage.py makemigrations --merge'", exception_message)
 
     def test_makemigrations_merge_no_conflict(self):
         """
@@ -780,7 +790,8 @@ class MakeMigrationsTests(MigrationTestBase):
         """
         makemigrations exits if no app is specified with 'empty' mode.
         """
-        with self.assertRaises(CommandError):
+        msg = 'You must supply at least one app label when using --empty.'
+        with self.assertRaisesMessage(CommandError, msg):
             call_command("makemigrations", empty=True)
 
     def test_makemigrations_empty_migration(self):
diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py
index ec55ab912e..40acd5ed61 100644
--- a/tests/migrations/test_operations.py
+++ b/tests/migrations/test_operations.py
@@ -1945,7 +1945,7 @@ class OperationTests(OperationTestBase):
             operation.database_backwards("test_runpython", editor, project_state, new_state)
         self.assertEqual(project_state.apps.get_model("test_runpython", "Pony").objects.count(), 0)
         # Now test we can't use a string
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, 'RunPython must be supplied with a callable'):
             migrations.RunPython("print 'ahahaha'")
         # And deconstruction
         definition = operation.deconstruct()
diff --git a/tests/model_fields/test_decimalfield.py b/tests/model_fields/test_decimalfield.py
index 17bbae7ffa..c8851c5d94 100644
--- a/tests/model_fields/test_decimalfield.py
+++ b/tests/model_fields/test_decimalfield.py
@@ -21,7 +21,8 @@ class DecimalFieldTests(TestCase):
         # Uses default rounding of ROUND_HALF_EVEN.
         self.assertEqual(f.to_python(2.0625), Decimal('2.062'))
         self.assertEqual(f.to_python(2.1875), Decimal('2.188'))
-        with self.assertRaises(ValidationError):
+        msg = "'abc' value must be a decimal number."
+        with self.assertRaisesMessage(ValidationError, msg):
             f.to_python('abc')
 
     def test_default(self):
diff --git a/tests/model_fields/test_floatfield.py b/tests/model_fields/test_floatfield.py
index c1c941b25c..481925cf11 100644
--- a/tests/model_fields/test_floatfield.py
+++ b/tests/model_fields/test_floatfield.py
@@ -21,8 +21,7 @@ class TestFloatField(TestCase):
         instance.size = instance
         msg = (
             'Tried to update field model_fields.FloatModel.size with a model '
-            'instance, %r. Use a value '
-            'compatible with FloatField.'
+            'instance, %r. Use a value compatible with FloatField.'
         ) % instance
         with transaction.atomic():
             with self.assertRaisesMessage(TypeError, msg):
@@ -30,5 +29,5 @@ class TestFloatField(TestCase):
         # Try setting field to object on retrieved object
         obj = FloatModel.objects.get(pk=instance.id)
         obj.size = obj
-        with self.assertRaises(TypeError):
+        with self.assertRaisesMessage(TypeError, msg):
             obj.save()
diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py
index 578ef00696..0cfc18659f 100644
--- a/tests/model_forms/tests.py
+++ b/tests/model_forms/tests.py
@@ -180,7 +180,7 @@ class ModelFormBaseTest(TestCase):
     def test_no_model_class(self):
         class NoModelModelForm(forms.ModelForm):
             pass
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, 'ModelForm has no model class specified.'):
             NoModelModelForm()
 
     def test_empty_fields_to_fields_for_model(self):
@@ -326,7 +326,7 @@ class ModelFormBaseTest(TestCase):
                 fields = ('name', 'age')
 
     def test_extra_field_modelform_factory(self):
-        with self.assertRaises(FieldError):
+        with self.assertRaisesMessage(FieldError, 'Unknown field(s) (no-field) specified for Person'):
             modelform_factory(Person, fields=['no-field', 'name'])
 
     def test_replace_field(self):
@@ -426,7 +426,8 @@ class ModelFormBaseTest(TestCase):
         form = PriceFormWithoutQuantity({'price': '6.00'})
         self.assertTrue(form.is_valid())
         price = form.save(commit=False)
-        with self.assertRaises(ValidationError):
+        msg = "{'quantity': ['This field cannot be null.']}"
+        with self.assertRaisesMessage(ValidationError, msg):
             price.full_clean()
 
         # The form should not validate fields that it doesn't contain even if they are
@@ -498,11 +499,12 @@ class ModelFormBaseTest(TestCase):
                 pass  # no model
 
         # Can't create new form
-        with self.assertRaises(ValueError):
+        msg = 'ModelForm has no model class specified.'
+        with self.assertRaisesMessage(ValueError, msg):
             InvalidModelForm()
 
         # Even if you provide a model instance
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, msg):
             InvalidModelForm(instance=Category)
 
     def test_subcategory_form(self):
@@ -1301,10 +1303,11 @@ class ModelFormBasicTests(TestCase):
             ["Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."]
         )
         self.assertEqual(f.cleaned_data, {'url': 'foo'})
-        with self.assertRaises(ValueError):
+        msg = "The Category could not be created because the data didn't validate."
+        with self.assertRaisesMessage(ValueError, msg):
             f.save()
         f = BaseCategoryForm({'name': '', 'slug': '', 'url': 'foo'})
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, msg):
             f.save()
 
     def test_multi_fields(self):
@@ -1597,7 +1600,8 @@ class ModelChoiceFieldTests(TestCase):
         # instantiated. This proves clean() checks the database during clean() rather
         # than caching it at time of instantiation.
         Category.objects.get(url='4th').delete()
-        with self.assertRaises(ValidationError):
+        msg = "['Select a valid choice. That choice is not one of the available choices.']"
+        with self.assertRaisesMessage(ValidationError, msg):
             f.clean(c4.id)
 
     def test_modelchoicefield_choices(self):
@@ -3045,7 +3049,11 @@ class LocalizedModelFormTest(TestCase):
         self.assertTrue(f.fields['right'].localize)
 
     def test_model_form_refuses_arbitrary_string(self):
-        with self.assertRaises(TypeError):
+        msg = (
+            "BrokenLocalizedTripleForm.Meta.localized_fields "
+            "cannot be a string. Did you mean to type: ('foo',)?"
+        )
+        with self.assertRaisesMessage(TypeError, msg):
             class BrokenLocalizedTripleForm(forms.ModelForm):
                 class Meta:
                     model = Triple
diff --git a/tests/model_inheritance/test_abstract_inheritance.py b/tests/model_inheritance/test_abstract_inheritance.py
index c496ac963b..f03bc3fa7e 100644
--- a/tests/model_inheritance/test_abstract_inheritance.py
+++ b/tests/model_inheritance/test_abstract_inheritance.py
@@ -150,10 +150,11 @@ class AbstractInheritanceTests(TestCase):
             def full_name(self):
                 return self.first_name + self.last_name
 
-        with self.assertRaises(FieldDoesNotExist):
+        msg = "Descendant has no field named %r"
+        with self.assertRaisesMessage(FieldDoesNotExist, msg % 'middle_name'):
             Descendant._meta.get_field('middle_name')
 
-        with self.assertRaises(FieldDoesNotExist):
+        with self.assertRaisesMessage(FieldDoesNotExist, msg % 'full_name'):
             Descendant._meta.get_field('full_name')
 
     def test_overriding_field_removed_by_concrete_model(self):
diff --git a/tests/model_inheritance/tests.py b/tests/model_inheritance/tests.py
index feff4a1407..e1eca65742 100644
--- a/tests/model_inheritance/tests.py
+++ b/tests/model_inheritance/tests.py
@@ -43,7 +43,7 @@ class ModelInheritanceTests(TestCase):
 
         # However, the CommonInfo class cannot be used as a normal model (it
         # doesn't exist as a model).
-        with self.assertRaises(AttributeError):
+        with self.assertRaisesMessage(AttributeError, "'CommonInfo' has no attribute 'objects'"):
             CommonInfo.objects.all()
 
     def test_reverse_relation_for_different_hierarchy_tree(self):
@@ -51,7 +51,12 @@ class ModelInheritanceTests(TestCase):
         # Restaurant object cannot access that reverse relation, since it's not
         # part of the Place-Supplier Hierarchy.
         self.assertQuerysetEqual(Place.objects.filter(supplier__name="foo"), [])
-        with self.assertRaises(FieldError):
+        msg = (
+            "Cannot resolve keyword 'supplier' into field. Choices are: "
+            "address, chef, chef_id, id, italianrestaurant, lot, name, "
+            "place_ptr, place_ptr_id, provider, rating, serves_hot_dogs, serves_pizza"
+        )
+        with self.assertRaisesMessage(FieldError, msg):
             Restaurant.objects.filter(supplier__name="foo")
 
     def test_model_with_distinct_accessors(self):
@@ -65,7 +70,8 @@ class ModelInheritanceTests(TestCase):
 
         # The Post model doesn't have an attribute called
         # 'attached_%(class)s_set'.
-        with self.assertRaises(AttributeError):
+        msg = "'Post' object has no attribute 'attached_%(class)s_set'"
+        with self.assertRaisesMessage(AttributeError, msg):
             getattr(post, "attached_%(class)s_set")
 
     def test_model_with_distinct_related_query_name(self):
diff --git a/tests/multiple_database/tests.py b/tests/multiple_database/tests.py
index 9edc58679a..8c4e8a8d8c 100644
--- a/tests/multiple_database/tests.py
+++ b/tests/multiple_database/tests.py
@@ -321,27 +321,39 @@ class QueryTestCase(TestCase):
 
         mark = Person.objects.using('other').create(name="Mark Pilgrim")
         # Set a foreign key set with an object from a different database
-        with self.assertRaises(ValueError):
+        msg = (
+            'Cannot assign "<Person: Marty Alchin>": the current database '
+            'router prevents this relation.'
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             with transaction.atomic(using='default'):
                 marty.edited.set([pro, dive])
 
         # Add to an m2m with an object from a different database
-        with self.assertRaises(ValueError):
+        msg = (
+            'Cannot add "<Book: Dive into Python>": instance is on '
+            'database "default", value is on database "other"'
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             with transaction.atomic(using='default'):
                 marty.book_set.add(dive)
 
         # Set a m2m with an object from a different database
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, msg):
             with transaction.atomic(using='default'):
                 marty.book_set.set([pro, dive])
 
         # Add to a reverse m2m with an object from a different database
-        with self.assertRaises(ValueError):
+        msg = (
+            'Cannot add "<Person: Marty Alchin>": instance is on '
+            'database "other", value is on database "default"'
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             with transaction.atomic(using='other'):
                 dive.authors.add(marty)
 
         # Set a reverse m2m with an object from a different database
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, msg):
             with transaction.atomic(using='other'):
                 dive.authors.set([mark, marty])
 
@@ -537,16 +549,20 @@ class QueryTestCase(TestCase):
         dive = Book.objects.using('other').create(title="Dive into Python", published=datetime.date(2009, 5, 4))
 
         # Set a foreign key with an object from a different database
-        with self.assertRaises(ValueError):
+        msg = (
+            'Cannot assign "<Person: Marty Alchin>": the current database '
+            'router prevents this relation.'
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             dive.editor = marty
 
         # Set a foreign key set with an object from a different database
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, msg):
             with transaction.atomic(using='default'):
                 marty.edited.set([pro, dive])
 
         # Add to a foreign key set with an object from a different database
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, msg):
             with transaction.atomic(using='default'):
                 marty.edited.add(dive)
 
@@ -655,7 +671,11 @@ class QueryTestCase(TestCase):
 
         # Set a one-to-one relation with an object from a different database
         alice_profile = UserProfile.objects.using('default').create(user=alice, flavor='chocolate')
-        with self.assertRaises(ValueError):
+        msg = (
+            'Cannot assign "<UserProfile: UserProfile object (1)>": the '
+            'current database router prevents this relation.'
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             bob.userprofile = alice_profile
 
         # BUT! if you assign a FK object when the base object hasn't
@@ -810,11 +830,19 @@ class QueryTestCase(TestCase):
         Review.objects.using('other').create(source="Python Weekly", content_object=dive)
 
         # Set a foreign key with an object from a different database
-        with self.assertRaises(ValueError):
+        msg = (
+            'Cannot assign "<ContentType: book>": the current database router '
+            'prevents this relation.'
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             review1.content_object = dive
 
         # Add to a foreign key set with an object from a different database
-        with self.assertRaises(ValueError):
+        msg = (
+            "<Review: Python Monthly> instance isn't saved. "
+            "Use bulk=False or save the object first."
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             with transaction.atomic(using='other'):
                 dive.reviews.add(review1)
 
@@ -913,11 +941,15 @@ class QueryTestCase(TestCase):
         # When you call __str__ on the query object, it doesn't know about using
         # so it falls back to the default. If the subquery explicitly uses a
         # different database, an error should be raised.
-        with self.assertRaises(ValueError):
+        msg = (
+            "Subqueries aren't allowed across different databases. Force the "
+            "inner query to be evaluated using `list(inner_query)`."
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             str(qs.query)
 
         # Evaluating the query shouldn't work, either
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, msg):
             for obj in qs:
                 pass
 
diff --git a/tests/null_queries/tests.py b/tests/null_queries/tests.py
index 221bd05946..342b43f126 100644
--- a/tests/null_queries/tests.py
+++ b/tests/null_queries/tests.py
@@ -32,11 +32,12 @@ class NullQueriesTests(TestCase):
         self.assertSequenceEqual(Choice.objects.exclude(choice=None).order_by('id'), [c1, c2])
 
         # Valid query, but fails because foo isn't a keyword
-        with self.assertRaises(FieldError):
+        msg = "Cannot resolve keyword 'foo' into field. Choices are: choice, id, poll, poll_id"
+        with self.assertRaisesMessage(FieldError, msg):
             Choice.objects.filter(foo__exact=None)
 
         # Can't use None on anything other than __exact and __iexact
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, 'Cannot use None as a query value'):
             Choice.objects.filter(id__gt=None)
 
         # Related managers use __exact=None implicitly if the object hasn't been saved.
diff --git a/tests/one_to_one/tests.py b/tests/one_to_one/tests.py
index 67eed38a93..82a061b736 100644
--- a/tests/one_to_one/tests.py
+++ b/tests/one_to_one/tests.py
@@ -226,7 +226,11 @@ class OneToOneTests(TestCase):
         setattr(p, 'restaurant', None)
 
         # You also can't assign an object of the wrong type here
-        with self.assertRaises(ValueError):
+        msg = (
+            'Cannot assign "<Place: Demon Dogs the place>": '
+            '"Place.restaurant" must be a "Restaurant" instance.'
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             setattr(p, 'restaurant', p)
 
         # Creation using keyword argument should cache the related object.
diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py
index 7be7cbbac4..85de08b184 100644
--- a/tests/prefetch_related/tests.py
+++ b/tests/prefetch_related/tests.py
@@ -199,14 +199,22 @@ class PrefetchRelatedTests(TestCase):
 
     def test_attribute_error(self):
         qs = Reader.objects.all().prefetch_related('books_read__xyz')
-        with self.assertRaises(AttributeError) as cm:
+        msg = (
+            "Cannot find 'xyz' on Book object, 'books_read__xyz' "
+            "is an invalid parameter to prefetch_related()"
+        )
+        with self.assertRaisesMessage(AttributeError, msg) as cm:
             list(qs)
 
         self.assertIn('prefetch_related', str(cm.exception))
 
     def test_invalid_final_lookup(self):
         qs = Book.objects.prefetch_related('authors__name')
-        with self.assertRaises(ValueError) as cm:
+        msg = (
+            "'authors__name' does not resolve to an item that supports "
+            "prefetching - this is an invalid parameter to prefetch_related()."
+        )
+        with self.assertRaisesMessage(ValueError, msg) as cm:
             list(qs)
 
         self.assertIn('prefetch_related', str(cm.exception))
@@ -337,14 +345,22 @@ class CustomPrefetchTests(TestCase):
 
     def test_ambiguous(self):
         # Ambiguous: Lookup was already seen with a different queryset.
-        with self.assertRaises(ValueError):
+        msg = (
+            "'houses' lookup was already seen with a different queryset. You "
+            "may need to adjust the ordering of your lookups."
+        )
+        with self.assertRaisesMessage(ValueError, msg):
             self.traverse_qs(
                 Person.objects.prefetch_related('houses__rooms', Prefetch('houses', queryset=House.objects.all())),
                 [['houses', 'rooms']]
             )
 
         # Ambiguous: Lookup houses_lst doesn't yet exist when performing houses_lst__rooms.
-        with self.assertRaises(AttributeError):
+        msg = (
+            "Cannot find 'houses_lst' on Person object, 'houses_lst__rooms' is "
+            "an invalid parameter to prefetch_related()"
+        )
+        with self.assertRaisesMessage(AttributeError, msg):
             self.traverse_qs(
                 Person.objects.prefetch_related(
                     'houses_lst__rooms',
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 75425f86ab..47f2ece35b 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -2957,7 +2957,8 @@ class WhereNodeTest(TestCase):
 class QuerySetExceptionTests(TestCase):
     def test_iter_exceptions(self):
         qs = ExtraInfo.objects.only('author')
-        with self.assertRaises(AttributeError):
+        msg = "'ManyToOneRel' object has no attribute 'attname'"
+        with self.assertRaisesMessage(AttributeError, msg):
             list(qs)
 
     def test_invalid_qs_list(self):
@@ -3735,9 +3736,10 @@ class TestTicket24279(TestCase):
 
 class TestInvalidValuesRelation(TestCase):
     def test_invalid_values(self):
-        with self.assertRaises(ValueError):
+        msg = "invalid literal for int() with base 10: 'abc'"
+        with self.assertRaisesMessage(ValueError, msg):
             Annotation.objects.filter(tag='abc')
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, msg):
             Annotation.objects.filter(tag__in=[123, 'abc'])
 
 
diff --git a/tests/redirects_tests/tests.py b/tests/redirects_tests/tests.py
index 5bce11d800..e7f5dfb97d 100644
--- a/tests/redirects_tests/tests.py
+++ b/tests/redirects_tests/tests.py
@@ -57,7 +57,11 @@ class RedirectTests(TestCase):
 
     @modify_settings(INSTALLED_APPS={'remove': 'django.contrib.sites'})
     def test_sites_not_installed(self):
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            'You cannot use RedirectFallbackMiddleware when '
+            'django.contrib.sites is not installed.'
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             RedirectFallbackMiddleware()
 
 
diff --git a/tests/reverse_lookup/tests.py b/tests/reverse_lookup/tests.py
index 0e0093f38e..b73b23e932 100644
--- a/tests/reverse_lookup/tests.py
+++ b/tests/reverse_lookup/tests.py
@@ -46,5 +46,9 @@ class ReverseLookupTests(TestCase):
         """
         If a related_name is given you can't use the field name instead
         """
-        with self.assertRaises(FieldError):
+        msg = (
+            "Cannot resolve keyword 'choice' into field. Choices are: "
+            "creator, creator_id, id, poll_choice, question, related_choice"
+        )
+        with self.assertRaisesMessage(FieldError, msg):
             Poll.objects.get(choice__name__exact="This is the answer")
diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py
index 7228af6e8e..eaedd506de 100644
--- a/tests/select_for_update/tests.py
+++ b/tests/select_for_update/tests.py
@@ -246,7 +246,8 @@ class SelectForUpdateTests(TransactionTestCase):
         A TransactionManagementError is raised
         when a select_for_update query is executed outside of a transaction.
         """
-        with self.assertRaises(transaction.TransactionManagementError):
+        msg = 'select_for_update cannot be used outside of a transaction.'
+        with self.assertRaisesMessage(transaction.TransactionManagementError, msg):
             list(Person.objects.all().select_for_update())
 
     @skipUnlessDBFeature('has_select_for_update')
@@ -257,7 +258,8 @@ class SelectForUpdateTests(TransactionTestCase):
         only when the query is executed.
         """
         people = Person.objects.all().select_for_update()
-        with self.assertRaises(transaction.TransactionManagementError):
+        msg = 'select_for_update cannot be used outside of a transaction.'
+        with self.assertRaisesMessage(transaction.TransactionManagementError, msg):
             list(people)
 
     @skipUnlessDBFeature('supports_select_for_update_with_limit')
diff --git a/tests/select_related/tests.py b/tests/select_related/tests.py
index 04f762f657..c19984e267 100644
--- a/tests/select_related/tests.py
+++ b/tests/select_related/tests.py
@@ -137,10 +137,6 @@ class SelectRelatedTests(TestCase):
                  .order_by('id')[0:1].get().genus.family.order.name)
             self.assertEqual(s, 'Diptera')
 
-    def test_depth_fields_fails(self):
-        with self.assertRaises(TypeError):
-            Species.objects.select_related('genus__family__order', depth=4)
-
     def test_none_clears_list(self):
         queryset = Species.objects.select_related('genus').select_related(None)
         self.assertIs(queryset.query.select_related, False)
diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py
index 1ab08cbd14..5a618954ed 100644
--- a/tests/settings_tests/tests.py
+++ b/tests/settings_tests/tests.py
@@ -238,7 +238,7 @@ class SettingsTests(SimpleTestCase):
             getattr(settings, 'TEST')
 
     def test_settings_delete_wrapped(self):
-        with self.assertRaises(TypeError):
+        with self.assertRaisesMessage(TypeError, "can't delete _wrapped."):
             delattr(settings, '_wrapped')
 
     def test_override_settings_delete(self):
diff --git a/tests/sitemaps_tests/test_http.py b/tests/sitemaps_tests/test_http.py
index 45b95d0958..b1797840b3 100644
--- a/tests/sitemaps_tests/test_http.py
+++ b/tests/sitemaps_tests/test_http.py
@@ -16,6 +16,10 @@ from .models import TestModel
 
 
 class HTTPSitemapTests(SitemapTestsBase):
+    use_sitemap_err_msg = (
+        'To use sitemaps, either enable the sites framework or pass a '
+        'Site/RequestSite object in your view.'
+    )
 
     def test_simple_sitemap_index(self):
         "A simple sitemap index can be rendered"
@@ -207,7 +211,7 @@ class HTTPSitemapTests(SitemapTestsBase):
         Sitemap.get_urls and no Site objects exist
         """
         Site.objects.all().delete()
-        with self.assertRaises(ImproperlyConfigured):
+        with self.assertRaisesMessage(ImproperlyConfigured, self.use_sitemap_err_msg):
             Sitemap().get_urls()
 
     @modify_settings(INSTALLED_APPS={'remove': 'django.contrib.sites'})
@@ -217,7 +221,7 @@ class HTTPSitemapTests(SitemapTestsBase):
         Sitemap.get_urls if Site objects exists, but the sites framework is not
         actually installed.
         """
-        with self.assertRaises(ImproperlyConfigured):
+        with self.assertRaisesMessage(ImproperlyConfigured, self.use_sitemap_err_msg):
             Sitemap().get_urls()
 
     def test_sitemap_item(self):
diff --git a/tests/staticfiles_tests/test_finders.py b/tests/staticfiles_tests/test_finders.py
index 20d3060092..9d5707cc2d 100644
--- a/tests/staticfiles_tests/test_finders.py
+++ b/tests/staticfiles_tests/test_finders.py
@@ -105,5 +105,10 @@ class TestMiscFinder(SimpleTestCase):
 
     @override_settings(MEDIA_ROOT='')
     def test_location_empty(self):
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            "The storage backend of the staticfiles finder "
+            "<class 'django.contrib.staticfiles.finders.DefaultStorageFinder'> "
+            "doesn't have a valid location."
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             finders.DefaultStorageFinder()
diff --git a/tests/syndication_tests/tests.py b/tests/syndication_tests/tests.py
index 28fe026414..e98ac354b0 100644
--- a/tests/syndication_tests/tests.py
+++ b/tests/syndication_tests/tests.py
@@ -452,7 +452,11 @@ class SyndicationFeedTest(FeedTestCase):
         An ImproperlyConfigured is raised if no link could be found for the
         item(s).
         """
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            'Give your Article class a get_absolute_url() method, or define '
+            'an item_link() method in your Feed class.'
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             self.client.get('/syndication/articles/')
 
     def test_template_feed(self):
diff --git a/tests/template_tests/syntax_tests/i18n/test_blocktrans.py b/tests/template_tests/syntax_tests/i18n/test_blocktrans.py
index d8b1e807af..425e748d53 100644
--- a/tests/template_tests/syntax_tests/i18n/test_blocktrans.py
+++ b/tests/template_tests/syntax_tests/i18n/test_blocktrans.py
@@ -332,11 +332,13 @@ class TranslationBlockTransTagTests(SimpleTestCase):
             self.assertEqual(rendered, '2 andere Super-Ergebnisse')
 
             # Misuses
-            with self.assertRaises(TemplateSyntaxError):
+            msg = "Unknown argument for 'blocktrans' tag: %r."
+            with self.assertRaisesMessage(TemplateSyntaxError, msg % 'month="May"'):
                 Template('{% load i18n %}{% blocktrans context with month="May" %}{{ month }}{% endblocktrans %}')
-            with self.assertRaises(TemplateSyntaxError):
+            msg = '"context" in %r tag expected exactly one argument.' % 'blocktrans'
+            with self.assertRaisesMessage(TemplateSyntaxError, msg):
                 Template('{% load i18n %}{% blocktrans context %}{% endblocktrans %}')
-            with self.assertRaises(TemplateSyntaxError):
+            with self.assertRaisesMessage(TemplateSyntaxError, msg):
                 Template(
                     '{% load i18n %}{% blocktrans count number=2 context %}'
                     '{{ number }} super result{% plural %}{{ number }}'
diff --git a/tests/template_tests/syntax_tests/test_filter_syntax.py b/tests/template_tests/syntax_tests/test_filter_syntax.py
index 738b3ed978..176475c04c 100644
--- a/tests/template_tests/syntax_tests/test_filter_syntax.py
+++ b/tests/template_tests/syntax_tests/test_filter_syntax.py
@@ -43,7 +43,8 @@ class FilterSyntaxTests(SimpleTestCase):
         """
         Raise TemplateSyntaxError for a nonexistent filter
         """
-        with self.assertRaises(TemplateSyntaxError):
+        msg = "Invalid filter: 'does_not_exist'"
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             self.engine.get_template('filter-syntax05')
 
     @setup({'filter-syntax06': '{{ var|fil(ter) }}'})
@@ -52,7 +53,7 @@ class FilterSyntaxTests(SimpleTestCase):
         Raise TemplateSyntaxError when trying to access a filter containing
         an illegal character
         """
-        with self.assertRaises(TemplateSyntaxError):
+        with self.assertRaisesMessage(TemplateSyntaxError, "Invalid filter: 'fil'"):
             self.engine.get_template('filter-syntax06')
 
     @setup({'filter-syntax07': "{% nothing_to_see_here %}"})
@@ -60,7 +61,11 @@ class FilterSyntaxTests(SimpleTestCase):
         """
         Raise TemplateSyntaxError for invalid block tags
         """
-        with self.assertRaises(TemplateSyntaxError):
+        msg = (
+            "Invalid block tag on line 1: 'nothing_to_see_here'. Did you "
+            "forget to register or load this tag?"
+        )
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             self.engine.get_template('filter-syntax07')
 
     @setup({'filter-syntax08': "{% %}"})
diff --git a/tests/template_tests/syntax_tests/test_with.py b/tests/template_tests/syntax_tests/test_with.py
index c1d501c94d..d8f24349f5 100644
--- a/tests/template_tests/syntax_tests/test_with.py
+++ b/tests/template_tests/syntax_tests/test_with.py
@@ -6,6 +6,7 @@ from ..utils import setup
 
 
 class WithTagTests(SimpleTestCase):
+    at_least_with_one_msg = "'with' expected at least one variable assignment"
 
     @setup({'with01': '{% with key=dict.key %}{{ key }}{% endwith %}'})
     def test_with01(self):
@@ -44,12 +45,12 @@ class WithTagTests(SimpleTestCase):
 
     @setup({'with-error01': '{% with dict.key xx key %}{{ key }}{% endwith %}'})
     def test_with_error01(self):
-        with self.assertRaises(TemplateSyntaxError):
+        with self.assertRaisesMessage(TemplateSyntaxError, self.at_least_with_one_msg):
             self.engine.render_to_string('with-error01', {'dict': {'key': 50}})
 
     @setup({'with-error02': '{% with dict.key as %}{{ key }}{% endwith %}'})
     def test_with_error02(self):
-        with self.assertRaises(TemplateSyntaxError):
+        with self.assertRaisesMessage(TemplateSyntaxError, self.at_least_with_one_msg):
             self.engine.render_to_string('with-error02', {'dict': {'key': 50}})
 
 
diff --git a/tests/template_tests/test_parser.py b/tests/template_tests/test_parser.py
index cd2f2ddc05..3dc731e0a9 100644
--- a/tests/template_tests/test_parser.py
+++ b/tests/template_tests/test_parser.py
@@ -40,7 +40,8 @@ class ParserTests(SimpleTestCase):
 
         # Filtered variables should reject access of attributes beginning with
         # underscores.
-        with self.assertRaises(TemplateSyntaxError):
+        msg = "Variables and attributes may not begin with underscores: 'article._hidden'"
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             FilterExpression("article._hidden|upper", p)
 
     def test_variable_parsing(self):
@@ -64,7 +65,8 @@ class ParserTests(SimpleTestCase):
 
         # Variables should reject access of attributes beginning with
         # underscores.
-        with self.assertRaises(TemplateSyntaxError):
+        msg = "Variables and attributes may not begin with underscores: 'article._hidden'"
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             Variable("article._hidden")
 
         # Variables should raise on non string type
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
index 06e2ce417c..80ee5a27b5 100644
--- a/tests/test_utils/tests.py
+++ b/tests/test_utils/tests.py
@@ -212,7 +212,8 @@ class AssertQuerysetEqualTests(TestCase):
     def test_undefined_order(self):
         # Using an unordered queryset with more than one ordered value
         # is an error.
-        with self.assertRaises(ValueError):
+        msg = 'Trying to compare non-ordered queryset against more than one ordered values'
+        with self.assertRaisesMessage(ValueError, msg):
             self.assertQuerysetEqual(
                 Person.objects.all(),
                 [repr(self.p1), repr(self.p2)]
@@ -415,23 +416,29 @@ class AssertTemplateUsedContextManagerTests(SimpleTestCase):
             self.assertTemplateUsed(response, 'template_used/base.html')
 
     def test_failure(self):
-        with self.assertRaises(TypeError):
+        msg = 'response and/or template_name argument must be provided'
+        with self.assertRaisesMessage(TypeError, msg):
             with self.assertTemplateUsed():
                 pass
 
-        with self.assertRaises(AssertionError):
+        msg = 'No templates used to render the response'
+        with self.assertRaisesMessage(AssertionError, msg):
             with self.assertTemplateUsed(''):
                 pass
 
-        with self.assertRaises(AssertionError):
+        with self.assertRaisesMessage(AssertionError, msg):
             with self.assertTemplateUsed(''):
                 render_to_string('template_used/base.html')
 
-        with self.assertRaises(AssertionError):
+        with self.assertRaisesMessage(AssertionError, msg):
             with self.assertTemplateUsed(template_name=''):
                 pass
 
-        with self.assertRaises(AssertionError):
+        msg = (
+            'template_used/base.html was not rendered. Following '
+            'templates were rendered: template_used/alternative.html'
+        )
+        with self.assertRaisesMessage(AssertionError, msg):
             with self.assertTemplateUsed('template_used/base.html'):
                 render_to_string('template_used/alternative.html')
 
diff --git a/tests/timezones/tests.py b/tests/timezones/tests.py
index 7c83ffbc47..926b8de704 100644
--- a/tests/timezones/tests.py
+++ b/tests/timezones/tests.py
@@ -123,7 +123,8 @@ class LegacyDatabaseTests(TestCase):
     @skipIfDBFeature('supports_timezones')
     def test_aware_datetime_unsupported(self):
         dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
-        with self.assertRaises(ValueError):
+        msg = 'backend does not support timezone-aware datetimes when USE_TZ is False.'
+        with self.assertRaisesMessage(ValueError, msg):
             Event.objects.create(dt=dt)
 
     def test_auto_now_and_auto_now_add(self):
@@ -647,7 +648,11 @@ class UnsupportedTimeZoneDatabaseTests(TestCase):
         connections.databases['tz']['TIME_ZONE'] = 'Asia/Bangkok'
         tz_conn = connections['tz']
         try:
-            with self.assertRaises(ImproperlyConfigured):
+            msg = (
+                "Connection 'tz' cannot set TIME_ZONE because its engine "
+                "handles time zones conversions natively."
+            )
+            with self.assertRaisesMessage(ImproperlyConfigured, msg):
                 tz_conn.cursor()
         finally:
             connections['tz'].close()       # in case the test fails
@@ -1033,7 +1038,8 @@ class TemplateTests(SimpleTestCase):
         self.assertEqual(tpl.render(Context()), "Europe/Paris")
 
     def test_get_current_timezone_templatetag_invalid_argument(self):
-        with self.assertRaises(TemplateSyntaxError):
+        msg = "'get_current_timezone' requires 'as variable' (got ['get_current_timezone'])"
+        with self.assertRaisesMessage(TemplateSyntaxError, msg):
             Template("{% load tz %}{% get_current_timezone %}").render()
 
     @skipIf(sys.platform.startswith('win'), "Windows uses non-standard time zone names")
diff --git a/tests/transaction_hooks/tests.py b/tests/transaction_hooks/tests.py
index 049211139d..ed3cf18be2 100644
--- a/tests/transaction_hooks/tests.py
+++ b/tests/transaction_hooks/tests.py
@@ -214,7 +214,8 @@ class TestConnectionOnCommit(TransactionTestCase):
 
         try:
             connection.set_autocommit(False)
-            with self.assertRaises(transaction.TransactionManagementError):
+            msg = 'on_commit() cannot be used in manual transaction management'
+            with self.assertRaisesMessage(transaction.TransactionManagementError, msg):
                 transaction.on_commit(should_never_be_called)
         finally:
             connection.set_autocommit(True)
diff --git a/tests/transactions/tests.py b/tests/transactions/tests.py
index 398a14be4e..7d4d4b777a 100644
--- a/tests/transactions/tests.py
+++ b/tests/transactions/tests.py
@@ -299,20 +299,21 @@ class AtomicMergeTests(TransactionTestCase):
 class AtomicErrorsTests(TransactionTestCase):
 
     available_apps = ['transactions']
+    forbidden_atomic_msg = "This is forbidden when an 'atomic' block is active."
 
     def test_atomic_prevents_setting_autocommit(self):
         autocommit = transaction.get_autocommit()
         with transaction.atomic():
-            with self.assertRaises(transaction.TransactionManagementError):
+            with self.assertRaisesMessage(transaction.TransactionManagementError, self.forbidden_atomic_msg):
                 transaction.set_autocommit(not autocommit)
         # Make sure autocommit wasn't changed.
         self.assertEqual(connection.autocommit, autocommit)
 
     def test_atomic_prevents_calling_transaction_methods(self):
         with transaction.atomic():
-            with self.assertRaises(transaction.TransactionManagementError):
+            with self.assertRaisesMessage(transaction.TransactionManagementError, self.forbidden_atomic_msg):
                 transaction.commit()
-            with self.assertRaises(transaction.TransactionManagementError):
+            with self.assertRaisesMessage(transaction.TransactionManagementError, self.forbidden_atomic_msg):
                 transaction.rollback()
 
     def test_atomic_prevents_queries_in_broken_transaction(self):
@@ -322,7 +323,11 @@ class AtomicErrorsTests(TransactionTestCase):
             with self.assertRaises(IntegrityError):
                 r2.save(force_insert=True)
             # The transaction is marked as needing rollback.
-            with self.assertRaises(transaction.TransactionManagementError):
+            msg = (
+                "An error occurred in the current transaction. You can't "
+                "execute queries until the end of the 'atomic' block."
+            )
+            with self.assertRaisesMessage(transaction.TransactionManagementError, msg):
                 r2.save(force_update=True)
         self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Haddock")
 
diff --git a/tests/update/tests.py b/tests/update/tests.py
index ba7ffd5c88..923af40305 100644
--- a/tests/update/tests.py
+++ b/tests/update/tests.py
@@ -123,7 +123,8 @@ class AdvancedTests(TestCase):
         We do not support update on already sliced query sets.
         """
         method = DataPoint.objects.all()[:2].update
-        with self.assertRaises(AssertionError):
+        msg = 'Cannot update a query once a slice has been taken.'
+        with self.assertRaisesMessage(AssertionError, msg):
             method(another_value='another thing')
 
     def test_update_respects_to_field(self):
diff --git a/tests/update_only_fields/tests.py b/tests/update_only_fields/tests.py
index 743b5fda8d..58ae94b7cc 100644
--- a/tests/update_only_fields/tests.py
+++ b/tests/update_only_fields/tests.py
@@ -5,6 +5,8 @@ from .models import Account, Employee, Person, Profile, ProxyEmployee
 
 
 class UpdateOnlyFieldsTests(TestCase):
+    msg = 'The following fields do not exist in this model or are m2m fields: %s'
+
     def test_update_fields_basic(self):
         s = Person.objects.create(name='Sara', gender='F')
         self.assertEqual(s.gender, 'F')
@@ -120,7 +122,7 @@ class UpdateOnlyFieldsTests(TestCase):
         a2 = Account.objects.create(num=2)
         e1.accounts.set([a1, a2])
 
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, self.msg % 'accounts'):
             e1.save(update_fields=['accounts'])
 
     def test_update_fields_inheritance(self):
@@ -201,10 +203,12 @@ class UpdateOnlyFieldsTests(TestCase):
     def test_update_fields_incorrect_params(self):
         s = Person.objects.create(name='Sara', gender='F')
 
-        with self.assertRaises(ValueError):
+        with self.assertRaisesMessage(ValueError, self.msg % 'first_name'):
             s.save(update_fields=['first_name'])
 
-        with self.assertRaises(ValueError):
+        # "name" is treated as an iterable so the output is something like
+        # "n, a, m, e" but the order isn't deterministic.
+        with self.assertRaisesMessage(ValueError, self.msg % ''):
             s.save(update_fields="name")
 
     def test_empty_update_fields(self):
diff --git a/tests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py
index ff204c5650..7f565fe487 100644
--- a/tests/urlpatterns_reverse/tests.py
+++ b/tests/urlpatterns_reverse/tests.py
@@ -1067,7 +1067,12 @@ class NoRootUrlConfTests(SimpleTestCase):
     """Tests for handler404 and handler500 if ROOT_URLCONF is None"""
 
     def test_no_handler_exception(self):
-        with self.assertRaises(ImproperlyConfigured):
+        msg = (
+            "The included URLconf 'None' does not appear to have any patterns "
+            "in it. If you see valid patterns in the file then the issue is "
+            "probably caused by a circular import."
+        )
+        with self.assertRaisesMessage(ImproperlyConfigured, msg):
             self.client.get('/test/me/')
 
 
diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py
index 19ae51b096..3900a58247 100644
--- a/tests/user_commands/tests.py
+++ b/tests/user_commands/tests.py
@@ -46,7 +46,7 @@ class CommandTests(SimpleTestCase):
 
     def test_explode(self):
         """ An unknown command raises CommandError """
-        with self.assertRaises(CommandError):
+        with self.assertRaisesMessage(CommandError, "Unknown command: 'explode'"):
             management.call_command(('explode',))
 
     def test_system_exit(self):
@@ -215,5 +215,6 @@ class CommandRunTests(AdminScriptTestCase):
 class UtilsTests(SimpleTestCase):
 
     def test_no_existent_external_program(self):
-        with self.assertRaises(CommandError):
+        msg = 'Error executing a_42_command_that_doesnt_exist_42'
+        with self.assertRaisesMessage(CommandError, msg):
             popen_wrapper(['a_42_command_that_doesnt_exist_42'])
diff --git a/tests/validators/tests.py b/tests/validators/tests.py
index 8620e7dc35..bc8ee7fb1d 100644
--- a/tests/validators/tests.py
+++ b/tests/validators/tests.py
@@ -334,7 +334,8 @@ class TestSimpleValidators(SimpleTestCase):
         self.assertEqual(repr(v), "ValidationError({'first': ['First Problem']})")
 
     def test_regex_validator_flags(self):
-        with self.assertRaises(TypeError):
+        msg = 'If the flags are set, regex must be a regular expression string.'
+        with self.assertRaisesMessage(TypeError, msg):
             RegexValidator(re.compile('a'), flags=re.IGNORECASE)
 
     def test_max_length_validator_message(self):