From f073797f4ca8d3e2736d99f76674e43640399c0d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 7 May 2007 12:34:18 +0000 Subject: [PATCH] Added assertFormError, assertTemplateUsed and assertTemplateNotUsed for use during unit testing. git-svn-id: http://code.djangoproject.com/svn/django/trunk@5156 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/test/testcases.py | 60 ++++++++++++++++++++- docs/testing.txt | 31 +++++++++-- tests/modeltests/test_client/models.py | 74 ++++++++++++++++++++++++-- tests/modeltests/test_client/urls.py | 2 + tests/modeltests/test_client/views.py | 24 +++++++++ tests/templates/base.html | 8 +++ tests/templates/form_view.html | 15 ++++++ tests/templates/login.html | 10 ++-- 8 files changed, 208 insertions(+), 16 deletions(-) create mode 100644 tests/templates/base.html create mode 100644 tests/templates/form_view.html diff --git a/django/test/testcases.py b/django/test/testcases.py index efe392590fe..80f55b20d39 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -77,4 +77,62 @@ class TestCase(unittest.TestCase): real_count = response.content.count(text) self.assertEqual(real_count, count, "Could only find %d of %d instances of '%s' in response" % (real_count, count, text)) - \ No newline at end of file + + def assertFormError(self, response, form, field, errors): + "Assert that a form used to render the response has a specific field error" + if not response.context: + self.fail('Response did not use any contexts to render the response') + + # If there is a single context, put it into a list to simplify processing + if not isinstance(response.context, list): + contexts = [response.context] + else: + contexts = response.context + + # If a single error string is provided, make it a list to simplify processing + if not isinstance(errors, list): + errors = [errors] + + # Search all contexts for the error. + found_form = False + for i,context in enumerate(contexts): + if form in context: + found_form = True + try: + for err in errors: + if field: + self.assertTrue(err in context[form].errors[field], + "The field '%s' on form '%s' in context %d does not contain the error '%s' (actual errors: %s)" % + (field, form, i, err, list(context[form].errors[field]))) + else: + self.assertTrue(err in context[form].non_field_errors(), + "The form '%s' in context %d does not contain the non-field error '%s' (actual errors: %s)" % + (form, i, err, list(context[form].non_field_errors()))) + except KeyError: + self.fail("The form '%s' in context %d does not contain the field '%s'" % (form, i, field)) + if not found_form: + self.fail("The form '%s' was not used to render the response" % form) + + def assertTemplateUsed(self, response, template_name): + "Assert that the template with the provided name was used in rendering the response" + if isinstance(response.template, list): + template_names = [t.name for t in response.template] + self.assertTrue(template_name in template_names, + "Template '%s' was not one of the templates used to render the response. Templates used: %s" % + (template_name, template_names)) + elif response.template: + self.assertEqual(template_name, response.template.name, + "Template '%s' was not used to render the response. Actual template was '%s'" % + (template_name, response.template.name)) + else: + self.fail('No templates used to render the response') + + def assertTemplateNotUsed(self, response, template_name): + "Assert that the template with the provided name was NOT used in rendering the response" + if isinstance(response.template, list): + self.assertFalse(template_name in [t.name for t in response.template], + "Template '%s' was used unexpectedly in rendering the response" % template_name) + elif response.template: + self.assertNotEqual(template_name, response.template.name, + "Template '%s' was used unexpectedly in rendering the response" % template_name) + \ No newline at end of file diff --git a/docs/testing.txt b/docs/testing.txt index 6cb763a85d5..b3b33e96783 100644 --- a/docs/testing.txt +++ b/docs/testing.txt @@ -462,15 +462,36 @@ Normal Python unit tests have a wide range of assertions, such as ``django.TestCase`` adds to these, providing some assertions that can be useful in testing the behavior of web sites. -``assertRedirects(response, expected_path)`` - Assert that the response received redirects the browser to the provided - path, and that the expected_path can be retrieved. - ``assertContains(response, text, count=1)`` - Assert that a response indicates that a page was retreived successfully, + Assert that a response indicates that a page was retrieved successfully, (i.e., the HTTP status code was 200), and that ``text`` occurs ``count`` times in the content of the response. +``assertFormError(response, form, field, errors)`` + Assert that a field on a form raised the provided list of errors when + rendered on the form. + + ``form`` is the name the form object 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 will be checked. + + ``errors`` is an error string, or a list of error strings, that are + expected as a result of form validation. + +``assertTemplateNotUsed(response, template_name)`` + Assert that the template with the given name was *not* used in rendering + the response. + +``assertRedirects(response, expected_path)`` + Assert that the response received redirects the browser to the provided + path, and that the expected_path can be retrieved. + +``assertTemplateUsed(response, template_name)`` + Assert that the template with the given name was used in rendering the + response. + + Running tests ============= diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index bad09482914..cd8dbe37d22 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -33,6 +33,13 @@ class ClientTest(TestCase): self.assertEqual(response.context['var'], 42) self.assertEqual(response.template.name, 'GET Template') + def test_no_template_view(self): + "Check that template usage assersions work then templates aren't in use" + response = self.client.get('/test_client/no_template_view/') + + # Check that the no template case doesn't mess with the template assertions + self.assertTemplateNotUsed(response, 'GET Template') + def test_get_post_view(self): "GET a view that normally expects POSTs" response = self.client.get('/test_client/post_view/', {}) @@ -40,6 +47,8 @@ class ClientTest(TestCase): # Check some response details self.assertEqual(response.status_code, 200) self.assertEqual(response.template.name, 'Empty GET Template') + self.assertTemplateUsed(response, 'Empty GET Template') + self.assertTemplateNotUsed(response, 'Empty POST Template') def test_empty_post(self): "POST an empty dictionary to a view" @@ -48,6 +57,8 @@ class ClientTest(TestCase): # Check some response details self.assertEqual(response.status_code, 200) self.assertEqual(response.template.name, 'Empty POST Template') + self.assertTemplateNotUsed(response, 'Empty GET Template') + self.assertTemplateUsed(response, 'Empty POST Template') def test_post(self): "POST some data to a view" @@ -88,7 +99,7 @@ class ClientTest(TestCase): } response = self.client.post('/test_client/form_view/', post_data) self.assertEqual(response.status_code, 200) - self.assertEqual(response.template.name, "Valid POST Template") + self.assertTemplateUsed(response, "Valid POST Template") def test_incomplete_data_form(self): "POST incomplete data to a form" @@ -97,8 +108,13 @@ class ClientTest(TestCase): 'value': 37 } response = self.client.post('/test_client/form_view/', post_data) - self.assertContains(response, 'This field is required', 3) - self.assertEqual(response.template.name, "Invalid POST Template") + self.assertContains(response, 'This field is required.', 3) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "Invalid POST Template") + + self.assertFormError(response, 'form', 'email', 'This field is required.') + self.assertFormError(response, 'form', 'single', 'This field is required.') + self.assertFormError(response, 'form', 'multi', 'This field is required.') def test_form_error(self): "POST erroneous data to a form" @@ -111,7 +127,57 @@ class ClientTest(TestCase): } response = self.client.post('/test_client/form_view/', post_data) self.assertEqual(response.status_code, 200) - self.assertEqual(response.template.name, "Invalid POST Template") + self.assertTemplateUsed(response, "Invalid POST Template") + + self.assertFormError(response, 'form', 'email', 'Enter a valid e-mail address.') + + def test_valid_form_with_template(self): + "POST valid data to a form using multiple templates" + post_data = { + 'text': 'Hello World', + 'email': 'foo@example.com', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view_with_template/', post_data) + self.assertContains(response, 'POST data OK') + self.assertTemplateUsed(response, "form_view.html") + self.assertTemplateUsed(response, 'base.html') + self.assertTemplateNotUsed(response, "Valid POST Template") + + def test_incomplete_data_form_with_template(self): + "POST incomplete data to a form using multiple templates" + post_data = { + 'text': 'Hello World', + 'value': 37 + } + response = self.client.post('/test_client/form_view_with_template/', post_data) + self.assertContains(response, 'POST data has errors') + self.assertTemplateUsed(response, 'form_view.html') + self.assertTemplateUsed(response, 'base.html') + self.assertTemplateNotUsed(response, "Invalid POST Template") + + self.assertFormError(response, 'form', 'email', 'This field is required.') + self.assertFormError(response, 'form', 'single', 'This field is required.') + self.assertFormError(response, 'form', 'multi', 'This field is required.') + + def test_form_error_with_template(self): + "POST erroneous data to a form using multiple templates" + post_data = { + 'text': 'Hello World', + 'email': 'not an email address', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view_with_template/', post_data) + self.assertContains(response, 'POST data has errors') + self.assertTemplateUsed(response, "form_view.html") + self.assertTemplateUsed(response, 'base.html') + self.assertTemplateNotUsed(response, "Invalid POST Template") + + self.assertFormError(response, 'form', 'email', 'Enter a valid e-mail address.') def test_unknown_page(self): "GET an invalid URL" diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py index 707ecc186d7..f63c486d016 100644 --- a/tests/modeltests/test_client/urls.py +++ b/tests/modeltests/test_client/urls.py @@ -2,11 +2,13 @@ from django.conf.urls.defaults import * import views urlpatterns = patterns('', + (r'^no_template_view/$', views.no_template_view), (r'^get_view/$', views.get_view), (r'^post_view/$', views.post_view), (r'^raw_post_view/$', views.raw_post_view), (r'^redirect_view/$', views.redirect_view), (r'^form_view/$', views.form_view), + (r'^form_view_with_template/$', views.form_view_with_template), (r'^login_protected_view/$', views.login_protected_view), (r'^session_view/$', views.session_view), (r'^broken_view/$', views.broken_view) diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index 36ec144cf61..3b7a57f4d0a 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -4,6 +4,11 @@ from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth.decorators import login_required from django.newforms.forms import Form from django.newforms import fields +from django.shortcuts import render_to_response + +def no_template_view(request): + "A simple view that expects a GET request, and returns a rendered template" + return HttpResponse("No template used") def get_view(request): "A simple view that expects a GET request, and returns a rendered template" @@ -79,6 +84,25 @@ def form_view(request): c = Context({'form': form}) return HttpResponse(t.render(c)) + +def form_view_with_template(request): + "A view that tests a simple form" + if request.method == 'POST': + form = TestForm(request.POST) + if form.is_valid(): + message = 'POST data OK' + else: + message = 'POST data has errors' + else: + form = TestForm() + message = 'GET form page' + return render_to_response('form_view.html', + { + 'form': form, + 'message': message + } + ) + def login_protected_view(request): "A simple view that is login protected." diff --git a/tests/templates/base.html b/tests/templates/base.html new file mode 100644 index 00000000000..611bc094a9a --- /dev/null +++ b/tests/templates/base.html @@ -0,0 +1,8 @@ + + + +

Django Internal Tests: {% block title %}{% endblock %}

+{% block content %} +{% endblock %} + + \ No newline at end of file diff --git a/tests/templates/form_view.html b/tests/templates/form_view.html new file mode 100644 index 00000000000..1487217547c --- /dev/null +++ b/tests/templates/form_view.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% block title %}Submit data{% endblock %} +{% block content %} +

{{ message }}

+
+{% if form.errors %} +

Please correct the errors below:

+{% endif %} + +
+ +{% endblock %} \ No newline at end of file diff --git a/tests/templates/login.html b/tests/templates/login.html index 8a0974c9a15..d55e9ddc75b 100644 --- a/tests/templates/login.html +++ b/tests/templates/login.html @@ -1,7 +1,6 @@ - - - -

Django Internal Tests: Login

+{% extends "base.html" %} +{% block title %}Login{% endblock %} +{% block content %} {% if form.has_errors %}

Your username and password didn't match. Please try again.

{% endif %} @@ -15,5 +14,4 @@ - - \ No newline at end of file +{% endblock %} \ No newline at end of file