# django-test-plus > Useful additions to Django's default TestCase. --- # Overview [](https://pypi.org/project/django-test-plus/) [](https://pypi.org/project/django-test-plus/) [](https://pypi.org/project/django-test-plus/) Useful additions to Django's default TestCase, from [REVSYS](https://www.revsys.com/). Let's face it, writing tests isn't always fun. Part of the reason for that is all of the boilerplate you end up writing. django-test-plus is an attempt to cut down on some of that when writing Django tests. We guarantee it will increase the time before you get carpal tunnel by at least 3 weeks! If you would like to get started testing your Django apps, or improve how your team is testing, we offer [TestStart](https://www.revsys.com/teststart/) to help your team dramatically improve your productivity. ## Why use django-test-plus? It is additive. test_plus.test.TestCase subclasses Django's, so everything you already know still works and you can adopt the helpers one at a time. - Reverse URLs inline. self.get('my-url-name') instead of importing reverse and threading it through every call. Args and kwargs pass straight through, and an already-reversed URL works too. - Assert on status codes readably. self.assert_http_200_ok() rather than self.assertEqual(response.status_code, 200), for [most of the status codes](methods/). - Skip the response variable. The last response and its context are stored for you, so the assertion helpers default to them. - Create users without ceremony. [make_user()](methods/) builds one with a sensible username, email, and password, takes a permission list, and honours a factory-boy factory if your User model needs one. - Check authentication in a line. [assertLoginRequired()](auth_helpers/) for any URL and HTTP method, plus a [login()](auth_helpers/) context manager for testing what each user should see. - Keep query counts honest. [assertNumQueriesLessThan()](low_query_counts/) and [assertGoodView()](low_query_counts/) catch N+1 regressions without pinning an exact number that churns on every change. - Unit test class-based views directly. [CBVTestCase](cbvtestcase/) invokes a view's methods without the URL resolution, middleware, and template stack, when that machinery is not what you are testing. - Works with unittest, pytest, and DRF. The same helpers, three ways. See below. Maintained by [REVSYS](https://www.revsys.com/), who have been building and maintaining Django projects since before 1.0. ## Installation ```shell pip install django-test-plus ``` ## Support - Python 3.10, 3.11, 3.12, 3.13, and 3.14, including the 3.14 free-threaded build (3.14t) - Django 4.2 LTS, 5.1, 5.2 LTS, 6.0, and 6.1 ## The same test, three ways The helpers are identical whichever style you write in. ### unittest Inherit from test_plus.test.TestCase rather than the normal django.test.TestCase: ```python from test_plus.test import TestCase class MyViewTests(TestCase): def test_the_view(self): self.get('my-url-name') self.response_200() self.assertResponseContains('

Hello, World!

') ``` ### pytest Ask for the tp fixture and every method above is available on it: ```python def test_the_view(tp): tp.get('my-url-name') tp.response_200() tp.assertResponseContains('

Hello, World!

') ``` ### pytest with Django REST Framework tp_api is the same thing backed by DRF's APIClient, so you can post JSON and assert on the result: ```python def test_the_api(tp_api): response = tp_api.post('my-api-view', extra={'format': 'json'}) assert response.status_code == 200 ``` Anything that touches the database needs pytest-django's db fixture alongside tp. That covers make_user(), the login() context, and the query counting helpers. See [pytest usage](usage/#pytest-usage) for the details. ## Where to next - [Usage](usage/): the pytest fixtures and testing DRF views - [Methods](methods/): requests, status assertions, response, and context helpers - [Authentication helpers](auth_helpers/): assertLoginRequired and the login() context - [Ensuring low query counts](low_query_counts/): assertNumQueriesLessThan and assertGoodView - [Testing class-based views](cbvtestcase/) - [Disable logging](disable_logging/) - [API reference](reference/) ## llms.txt This documentation is available in the [llms.txt](https://llmstxt.org/) format, a Markdown convention suited to LLMs and AI coding assistants. Two files are published: - [llms.txt](https://django-test-plus.readthedocs.io/en/latest/llms.txt): a short description of the project plus links to each section of the documentation. The structure is described [here](https://llmstxt.org/#format). - [llms-full.txt](https://django-test-plus.readthedocs.io/en/latest/llms-full.txt): the same index with the content of every page included inline, including the generated API reference. Every page is also published as Markdown alongside its HTML, so you can link an assistant at a single section rather than the whole corpus. Append .md to the page name: ```text https://django-test-plus.readthedocs.io/en/latest/usage.md https://django-test-plus.readthedocs.io/en/latest/methods.md https://django-test-plus.readthedocs.io/en/latest/reference.md ``` These files are not picked up automatically by IDEs or coding agents today, but most will use them if you supply a link or paste the text. --- # Usage To use django-test-plus, have your tests inherit from test_plus.test.TestCase rather than the normal django.test.TestCase: ```python from test_plus.test import TestCase class MyViewTests(TestCase): ... ``` This is sufficient to get things rolling, but you are encouraged to create your own sub-classes for your projects. This will allow you to add your own project-specific helper methods. For example, if you have a Django project named 'myproject', you might create the following in myproject/test.py: ```python from test_plus.test import TestCase as PlusTestCase class TestCase(PlusTestCase): pass ``` And then in your tests use: ```python from myproject.test import TestCase class MyViewTests(TestCase): ... ``` This import, which is similar to the way you would import Django's TestCase, is also valid: ```python from test_plus import TestCase ``` ## pytest Usage You can get a TestCase like object as a pytest fixture now by asking for tp. All of the methods below would then work in pytest functions. For example: ```python def test_url_reverse(tp): expected_url = '/api/' reversed_url = tp.reverse('api') assert expected_url == reversed_url ``` Everything documented in methods, auth_helpers, low_query_counts, and cbvtestcase is available on tp. Anywhere those pages write self.(), a pytest test writes tp.(). The same test written both ways: ```python # unittest style from test_plus.test import TestCase class MyViewTests(TestCase): def test_the_view(self): self.get('my-url-name') self.response_200() self.assertInContext('some-key') def test_auth(self): user = self.make_user('u1') self.assertLoginRequired('my-protected-view') with self.login(user): self.get_check_200('my-protected-view') # pytest style def test_the_view(tp): tp.get('my-url-name') tp.response_200() tp.assertInContext('some-key') def test_auth(tp, db): user = tp.make_user('u1') tp.assertLoginRequired('my-protected-view') with tp.login(user): tp.get_check_200('my-protected-view') ``` Note that tp does not manage database access for you the way django.test.TestCase does. Ask for pytest-django's db fixture (or apply @pytest.mark.django_db) in any test that touches the database. That includes make_user() and the login() context, and also the query counting helpers assertNumQueriesLessThan() and assertGoodView(), which open a database connection in order to count. The pytest plugin is auto-registered via pytest11, so no extra configuration is required beyond installing the package and pytest-django. In addition to tp and tp_api, the plugin also provides a raw api_client fixture: ```python def test_api_client(api_client): response = api_client.get("/api/") assert response.status_code == 200 ``` The tp_api fixture will provide a TestCase that uses django-rest-framework's `APIClient()`: ```python def test_url_reverse(tp_api): response = tp_api.client.post("myapi", format="json") assert response.status_code == 200 ``` ## Testing DRF views To take advantage of the convenience of DRF's test client, you can create a subclass of TestCase and set the client_class property: ```python from test_plus import TestCase from rest_framework.test import APIClient class APITestCase(TestCase): client_class = APIClient ``` For convenience, test_plus ships with APITestCase, which does just that: ```python from test_plus import APITestCase class MyAPITestCase(APITestCase): def test_post(self): data = {'testing': {'prop': 'value'}} self.post('view-json', data=data, extra={'format': 'json'}) self.response_200() ``` Note that using APITestCase requires Django >= 1.8 and having installed django-rest-framework. --- # Methods These examples are written in the unittest style, on a TestCase subclass. Every method here is also available on the pytest tp fixture. Write tp.get('my-url-name') where these read self.get('my-url-name'). See [pytest usage](../usage/#pytest-usage) for the details. ## reverse(url_name, *args, **kwargs) When testing views you often find yourself needing to reverse the URL's name. With django-test-plus there is no need for the from django.core.urlresolvers import reverse boilerplate. Instead, use: ```python def test_something(self): url = self.reverse('my-url-name') slug_url = self.reverse('name-takes-a-slug', slug='my-slug') pk_url = self.reverse('name-takes-a-pk', pk=12) ``` As you can see our reverse also passes along any args or kwargs you need to pass in. ## get(url_name, follow=False, *args, **kwargs) Another thing you do often is HTTP get urls. Our get() method assumes you are passing in a named URL with any args or kwargs necessary to reverse the url_name. If needed, place kwargs for TestClient.get() in an 'extra' dictionary.: ```python def test_get_named_url(self): response = self.get('my-url-name') # Get XML data via AJAX request xml_response = self.get( 'my-url-name', extra={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}) ``` When using this get method two other things happen for you: we store the last response in self.last\_response and the response's Context in self.context. So instead of: ```python def test_default_django(self): response = self.client.get(reverse('my-url-name')) self.assertTrue('foo' in response.context) self.assertEqual(response.context['foo'], 12) ``` You can write: ```python def test_testplus_get(self): self.get('my-url-name') self.assertInContext('foo') self.assertEqual(self.context['foo'], 12) ``` It's also smart about already reversed URLs, so you can be lazy and do: ```python def test_testplus_get(self): url = self.reverse('my-url-name') self.get(url) self.response_200() ``` If you need to pass query string parameters to your url name, you can do so like this. Assuming the name 'search' maps to '/search/' then: ```python def test_testplus_get_query(self): self.get('search', data={'query': 'testing'}) ``` Would GET /search/?query=testing ## post(url_name, follow=False, *args, **kwargs) Our post() method takes a named URL, an optional dictionary of data you wish to post and any args or kwargs necessary to reverse the url_name. If needed, place kwargs for TestClient.post() in an 'extra' dictionary.: ```python def test_post_named_url(self): response = self.post('my-url-name', data={'coolness-factor': 11.0}, extra={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}) ``` ## put(url_name, follow=False, *args, **kwargs) To support all HTTP methods ## patch(url_name, follow=False, *args, **kwargs) To support all HTTP methods ## head(url_name, follow=False, *args, **kwargs) To support all HTTP methods ## trace(url_name, follow=False, *args, **kwargs) To support all HTTP methods ## options(url_name, follow=False, *args, **kwargs) To support all HTTP methods ## delete(url_name, follow=False, *args, **kwargs) To support all HTTP methods ## get_context(key) Often you need to get things out of the template context: ```python def test_context_data(self): self.get('my-view-with-some-context') slug = self.get_context('slug') ``` ## assertInContext(key) You can ensure a specific key exists in the last response's context by using: ```python def test_in_context(self): self.get('my-view-with-some-context') self.assertInContext('some-key') ``` ## assertContext(key, value) We can get context values and ensure they exist, but we can also test equality while we're at it. This asserts that key == value: ```python def test_in_context(self): self.get('my-view-with-some-context') self.assertContext('some-key', 'expected value') ``` ## assert_http_XXX_\(response, msg=None) - status code checking Another test you often need to do is check that a response has a certain HTTP status code. With Django's default TestCase you would write: ```python from django.core.urlresolvers import reverse def test_status(self): response = self.client.get(reverse('my-url-name')) self.assertEqual(response.status_code, 200) ``` With django-test-plus you can shorten that to be: ```python def test_better_status(self): response = self.get('my-url-name') self.assert_http_200_ok(response) ``` Django-test-plus provides an assertion for every status code documented in the [MDN HTTP status reference](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status), plus the non-standard 509. They live in their own [mixin](https://github.com/revsys/django-test-plus/blob/main/test_plus/status_codes.py), are listed in the [API reference](../reference/), and should be searchable if you're using an IDE like PyCharm. Earlier versions of django-test-plus used assertion methods in the pattern of response_###(). Those are still supported and are not going away, so there is no need to migrate existing tests. See below for a list of them. Each of the assertion methods takes an optional Django test client response and a string msg argument that, if specified, is used as the error message when a failure occurs. The methods, assert_http_301_moved_permanently and assert_http_302_found also take an optional url argument that if passed, will check to make sure the response.url matches. If it's available, the assert_http_###_ methods will use the last response. So you can do:: ```python def test_status(self): self.get('my-url-name') self.assert_http_200_ok() ``` Which is a bit shorter. The response_###() methods, which remain fully supported, include: - response_200() - response_201() - response_204() - response_301() - response_302() - response_400() - response_401() - response_403() - response_404() - response_405() - response_409() - response_410() All of which take an optional Django test client response and a str msg argument that, if specified, is used as the error message when a failure occurs. Just like the assert_http_###_() methods, these methods will use the last response if it's available. ## assertResponseContains(text, response=None, html=True) You often want to check that the last response contains a chunk of HTML. With Django's default TestCase you would write: ```python from django.core.urlresolvers import reverse def test_response_contains(self): response = self.client.get(reverse('hello-world')) self.assertContains(response, '

Hello, World!

', html=True) ``` With django-test-plus you can shorten that to be: ```python def test_response_contains(self): self.get('hello-world') self.assertResponseContains('

Hello, World!

') ``` ## assertResponseNotContains(text, response=None, html=True) The inverse of the above test, this method makes sure the last response does not include the chunk of HTML: ```python def test_response_not_contains(self): self.get('hello-world') self.assertResponseNotContains('

Hello, Frank!

') ``` ## assertResponseHeaders(headers, response=None) Sometimes your views or middleware will set custom headers: ```python def test_custom_headers(self): self.get('my-url-name') self.assertResponseHeaders({'X-Custom-Header': 'Foo'}) self.assertResponseHeaders({'X-Does-Not-Exist': None}) ``` You might also want to check standard headers: ```python def test_content_type(self): self.get('my-json-view') self.assertResponseHeaders({'Content-Type': 'application/json'}) ``` ## assertResponseTemplateUsed(template_name, response=None) You can check that a specific template was used to render the last response: ```python def test_template_used(self): self.get('my-view') self.assertResponseTemplateUsed('my_template.html') ``` This is a convenience wrapper around Django's assertTemplateUsed that automatically uses self.last_response if no response is provided. ## assertResponseTemplateNotUsed(template_name, response=None) You can check that a specific template was not used to render the last response: ```python def test_template_not_used(self): self.get('my-view') self.assertResponseTemplateNotUsed('other_template.html') ``` This is a convenience wrapper around Django's assertTemplateNotUsed that automatically uses self.last_response if no response is provided. ## assertResponseMessages(expected_messages, response=None, ordered=True) Available in Django 5.0+ Convenience wrapper for Django's MessagesTestMixin.assertMessages that uses self.last_response by default. For full details on the underlying assertion, see the [Django documentation on testing messages](https://docs.djangoproject.com/en/stable/ref/contrib/messages/#testing). Example usage: ```python from django.contrib.messages import Message from django.contrib.messages.constants import SUCCESS, ERROR def test_success_message(self): self.post('my-form-view', data={'name': 'Test'}) expected_messages = [ Message(level=SUCCESS, message='Form saved successfully!'), ] self.assertResponseMessages(expected_messages) def test_multiple_messages(self): self.post('my-form-view', data={'invalid': 'data'}) expected_messages = [ Message(level=ERROR, message='Name is required.'), Message(level=ERROR, message='Email is required.'), ] self.assertResponseMessages(expected_messages) ``` For Django versions before 5.0, this method will raise a NotImplementedError. To skip tests conditionally: ```python import unittest import django @unittest.skipIf(django.VERSION < (5, 0), "assertResponseMessages requires Django 5.0+") def test_messages(self): self.get('view-with-messages') expected_messages = [Message(level=SUCCESS, message='Done!')] self.assertResponseMessages(expected_messages) ``` ## get_check_200(url_name, *args, **kwargs) GETing and checking views return status 200 is a common test. This method makes it more convenient: ```python def test_even_better_status(self): response = self.get_check_200('my-url-name') ``` ## assertRedirects(response, expected_url, ...) and assertURLEqual(url1, url2) Both are Django's own assertions, re-exported so they are available on TestCase without an extra import. assertURLEqual compares two URLs for equality, ignoring the ordering of query string parameters: ```python def test_urls_match(self): self.assertURLEqual('/search/?a=1&b=2', '/search/?b=2&a=1') ``` ## print_form_errors(response_or_form=None) When debugging a failing test for a view with a form, this method helps you quickly look at any form errors. It accepts either a response or a form instance, and defaults to self.last_response: ```python def test_form_errors(self): self.post('my-form-view', data={}) self.print_form_errors() def test_form_errors_explicit_response(self): resp = self.post('my-form-view', data={}) self.print_form_errors(resp) def test_form_errors_from_a_form(self): form = MyForm(data={}) self.print_form_errors(form) ``` ## make_user(username='testuser', password='password', perms=None) When testing out views you often need to create various users to ensure all of your logic is safe and sound. To make this process easier, this method will create a user for you: ```python def test_user_stuff(self) user1 = self.make_user('u1') user2 = self.make_user('u2') ``` If creating a User in your project is more complicated, say for example you removed the username field from the default Django Auth model, you can provide a [Factory Boy](https://factoryboy.readthedocs.org/en/latest/) factory to create it or override this method on your own sub-class. To use a Factory Boy factory, create your class like this: ```python from test_plus.test import TestCase from .factories import UserFactory class MySpecialTest(TestCase): user_factory = UserFactory def test_special_creation(self): user1 = self.make_user('u1') ``` NOTE: Users created by this method will have their password set to the string 'password' by default, in order to ease testing. If you need a specific password, override the password parameter. You can also pass in user permissions by passing in a string of '.' or '.*'. For example: ```python user2 = self.make_user(perms=['myapp.create_widget', 'otherapp.*']) ``` --- # Authentication Helpers ## assertLoginRequired(url_name, *args, method='get', **kwargs) This method helps you test that a given named URL requires authorization: ```python def test_auth(self): self.assertLoginRequired('my-restricted-url') self.assertLoginRequired('my-restricted-object', pk=12) self.assertLoginRequired('my-restricted-object', slug='something') ``` Like self.get() and friends, a plain URL works too when the name cannot be reversed: ```python def test_auth_by_url(self): self.assertLoginRequired('/restricted/') ``` Pass method to check a verb other than GET, which is useful for views that only accept writes: ```python def test_auth_on_post(self): self.assertLoginRequired('my-restricted-url', method='post') ``` method accepts any verb supported by request(): get, post, put, patch, head, trace, options, and delete. The same thing with the pytest tp fixture (see [pytest usage](../usage/#pytest-usage)): ```python def test_auth(tp): tp.assertLoginRequired('my-restricted-url') tp.assertLoginRequired('my-restricted-url', method='post') ``` ## login context Along with ensuing a view requires login and creating users, the next thing you end up doing is logging in as various users to test our your restriction logic: ```python def test_restrictions(self): user1 = self.make_user('u1') user2 = self.make_user('u2') self.assertLoginRequired('my-protected-view') with self.login(username=user1.username, password='password'): response = self.get('my-protected-view') # Test user1 sees what they should be seeing with self.login(username=user2.username, password='password'): response = self.get('my-protected-view') # Test user2 see what they should be seeing ``` Since we're likely creating our users using make_user() from above, the login context assumes the password is 'password' unless specified otherwise. Therefore you you can do: ```python def test_restrictions(self): user1 = self.make_user('u1') with self.login(username=user1.username): response = self.get('my-protected-view') ``` We can also derive the username if we're using make_user() so we can shorten that up even further like this: ```python def test_restrictions(self): user1 = self.make_user('u1') with self.login(user1): response = self.get('my-protected-view') ``` The login context works the same way on the pytest tp fixture. Because tp does not set up database access on its own, ask for pytest-django's db fixture in any test that creates a user: ```python def test_restrictions(tp, db): user1 = tp.make_user('u1') with tp.login(user1): response = tp.get('my-protected-view') ``` --- # Ensuring low query counts ## assertNumQueriesLessThan(number) - context Django provides [assertNumQueries](https://docs.djangoproject.com/en/1.8/topics/testing/tools/#django.test.TransactionTestCase.assertNumQueries) which is great when your code generates a specific number of queries. However, if this number varies due to the nature of your data, with this method you can still test to ensure the code doesn't start producing a ton more queries than you expect: ```python def test_something_out(self): with self.assertNumQueriesLessThan(7): self.get('some-view-with-6-queries') ``` ## assertGoodView(url_name, *args, verbose=False, **kwargs) This method does a few things for you. It: - Retrieves the name URL - Ensures the view does not generate more than 50 queries - Ensures the response has status code 200 - Returns the response Often a wide, sweeping test like this is better than no test at all. You can use it like this: ```python def test_better_than_nothing(self): response = self.assertGoodView('my-url-name') ``` Both helpers are available on the pytest tp fixture too (see [pytest usage](../usage/#pytest-usage)). Both count queries, so they need database access. Ask for pytest-django's db fixture alongside tp: ```python def test_better_than_nothing(tp, db): response = tp.assertGoodView('my-url-name') def test_something_out(tp, db): with tp.assertNumQueriesLessThan(7): tp.get('some-view-with-6-queries') ``` --- # Testing class-based "generic" views The TestCase methods get() and post() work for both function-based and class-based views. However, in doing so they invoke Django's URL resolution, middleware, template processing, and decorator systems. For integration testing this is desirable, as you want to ensure your URLs resolve properly, view permissions are enforced, etc. For unit testing this is costly because all these Django request/response systems are invoked in addition to your method, and they typically do not affect the end result. Class-based views (derived from Django's generic.models.View class) contain methods and mixins which makes granular unit testing (more) feasible. Quite often usage of a generic view class comprises a simple method override. Invoking the entire view and the Django request/response stack is a waste of time... you really want to test the overridden method directly. CBVTestCase to the rescue! As with TestCase above, have your tests inherit from test_plus.test.CBVTestCase rather than TestCase like so: ```python from test_plus.test import CBVTestCase class MyViewTests(CBVTestCase): ``` ## Methods ### get_instance(cls, initkwargs=None, request=None, *args, **kwargs) This core method simplifies the instantiation of your class, giving you a way to invoke class methods directly. Returns an instance of cls, initialized with initkwargs. Sets request, args, and kwargs attributes on the class instance. args and kwargs are the same values you would pass to reverse(). Sample usage: ```python from django.views import generic from test_plus.test import CBVTestCase class MyViewClass(generic.DetailView) def get_context_data(self, **kwargs): kwargs = super(MyViewClass, self).get_context_data(**kwargs) if hasattr(self.request, 'some_data'): kwargs.update({ 'some_data': self.request.some_data }) if hasattr(self, 'special_value'): kwargs.update({ 'special_value': self.special_value }) return kwargs class MyViewTests(CBVTestCase): def test_context_data(self): my_view = self.get_instance(MyViewClass, initkwargs={'special_value': 42}) context = my_view.get_context_data() self.assertContext('special_value', 42) def test_request_attribute(self): request = django.test.RequestFactory().get('/') request.some_data = 5 my_view = self.get_instance(MyViewClass, request=request) context = my_view.get_context_data() self.assertContext('some_data', 5) ``` ### get(cls, *args, **kwargs) Invokes cls.get() and returns the response, rendering template if possible. Builds on the CBVTestCase.get_instance() foundation. All test_plus.test.TestCase methods are valid, so the following works: ```python response = self.get(MyViewClass) self.assertContext('my_key', expected_value) ``` All test_plus TestCase side-effects are honored and all test_plus TestCase assertion methods work with CBVTestCase.get(). If you need special request attributes, i.e. 'user', you can create a custom Request with RequestFactory, assign to request.user, and use that in the get(): ```python def test_request_attribute(self): request = django.test.RequestFactory().get('/') request.user = some_user self.get(MyViewClass, request=request, pk=data.pk) self.assertContext('user', some_user) ``` NOTE: This method bypasses Django's middleware, and therefore context variables created by middleware are not available. If this affects your template/context testing you should use TestCase instead of CBVTestCase. ### post(cls, *args, **kwargs) Invokes cls.post() and returns the response, rendering template if possible. Builds on the CBVTestCase.get_instance() foundation. Example: ```python response = self.post(MyViewClass, data={'search_term': 'revsys'}) self.response_200(response) self.assertContext('company_name', 'RevSys') ``` All test_plus TestCase side-effects are honored and all test_plus TestCase assertion methods work with CBVTestCase.post(). If you need special request attributes, i.e. 'user', you can create a custom Request with RequestFactory, assign to request.user, and use that in the post(): ```python def test_request_attribute(self): request = django.test.RequestFactory().post('/') request.user = some_user self.post(MyViewClass, request=request, pk=self.data.pk, data={}) self.assertContext('user', some_user) ``` NOTE: This method bypasses Django's middleware, and therefore context variables created by middleware are not available. If this affects your template/context testing you should use TestCase instead of CBVTestCase. ### get_check_200(cls, initkwargs=None, *args, **kwargs) Works just like TestCase.get_check_200(). Caller must provide a view class instead of a URL name or path parameter. All test_plus TestCase side-effects are honored and all test_plus TestCase assertion methods work with CBVTestCase.post(). ### assertGoodView(cls, initkwargs=None, *args, **kwargs) Works just like TestCase.assertGoodView(). Caller must provide a view class instead of a URL name or path parameter. All test_plus TestCase side-effects are honored and all test_plus TestCase assertion methods work with CBVTestCase.post(). --- # Disable logging You can disable logging during testing by changing the [TEST_RUNNER](https://docs.djangoproject.com/en/1.8/topics/testing/advanced/#using-different-testing-frameworks) in your settings file to: ```python TEST_RUNNER = 'test_plus.runner.NoLoggingRunner' ``` --- # API reference ## test_plus.test ### TestCase Bases: [TestCase](https://docs.djangoproject.com/en/stable/_objects/topics/testing/tools/#django.test.TestCase), [BaseTestCase](./#test_plus.test.BaseTestCase) Django TestCase with helpful additional features ### BaseTestCase Bases: [StatusCodeAssertionMixin](./#test_plus.status_codes.StatusCodeAssertionMixin) Django TestCase with helpful additional features #### print_form_errors(response_or_form=None) A utility method for quickly debugging responses with form errors. #### request(method_name, url_name, *args, **kwargs) Request url by name using reverse() through method If reverse raises NoReverseMatch attempt to use it as a URL. #### response_200(response=None, msg=None) Given response has status_code 200 #### response_201(response=None, msg=None) Given response has status_code 201 #### response_204(response=None, msg=None) Given response has status_code 204 #### response_301(response=None, msg=None) Given response has status_code 301 #### response_302(response=None, msg=None) Given response has status_code 302 #### response_400(response=None, msg=None) Given response has status_code 400 #### response_401(response=None, msg=None) Given response has status_code 401 #### response_403(response=None, msg=None) Given response has status_code 403 #### response_404(response=None, msg=None) Given response has status_code 404 #### response_405(response=None, msg=None) Given response has status_code 405 #### response_409(response=None, msg=None) Given response has status_code 409 #### response_410(response=None, msg=None) Given response has status_code 410 #### get_check_200(url, *args, **kwargs) Test that we can GET a page and it returns a 200 #### assertLoginRequired(url, *args, method='get', **kwargs) Ensure login is required to access this URL via (default GET) url may be a url name or a plain URL, matching what request() accepts. #### login(*args, **credentials) Login a user #### reverse(name, *args, **kwargs) Reverse a url, convenience to avoid having to import reverse in tests #### make_user(username='testuser', password='password', perms=None) classmethod Build a user with and password of 'password' for testing purposes. #### assertGoodView(url_name, *args, verbose=False, **kwargs) Quick-n-dirty testing of a given url name. Ensures URL returns a 200 status and that generates less than 50 database queries. #### assertResponseContains(text, response=None, html=True, **kwargs) Convenience wrapper for assertContains #### assertResponseNotContains(text, response=None, html=True, **kwargs) Convenience wrapper for assertNotContains #### assertResponseTemplateUsed(template_name, response=None, **kwargs) Convenience wrapper for assertTemplateUsed #### assertResponseTemplateNotUsed(template_name, response=None, **kwargs) Convenience wrapper for assertTemplateNotUsed #### assertResponseHeaders(headers, response=None) Check that the headers in the response are as expected. Only headers defined in headers are compared, other keys present on the response will be ignored. Parameters: Name Type Description Default headers :class:`collections.Mapping` Mapping of header names to expected values required response :class:`django.http.response.HttpResponse` Response to check headers against None #### assertResponseMessages(expected_messages, response=None, ordered=True) Convenience wrapper for assertMessages that uses the last response by default. Tests that the messages in the response match the expected messages. Parameters: Name Type Description Default expected_messages List of Message objects to compare against required response Response to check messages against (defaults to last_response) None ordered If True, messages must match in order (default: True) True ### CBVTestCase Bases: [TestCase](./#test_plus.test.TestCase) Directly calls class-based generic view methods, bypassing the Django test Client. This process bypasses middleware invocation and URL resolvers. Example usage: ```text from myapp.views import MyClass class MyClassTest(CBVTestCase): def test_special_method(self): request = RequestFactory().get('/') instance = self.get_instance(MyClass, request=request) # invoke a MyClass method result = instance.special_method() # make assertions self.assertTrue(result) ``` #### get_instance(view_cls, *args, **kwargs) staticmethod Returns a decorated instance of a class-based generic view class. Use initkwargs to set expected class attributes. For example, set the object attribute on MyDetailView class: ```text instance = self.get_instance(MyDetailView, initkwargs={'object': obj}, request) ``` because SingleObjectMixin (part of generic.DetailView) expects self.object to be set before invoking get_context_data(). Pass a "request" kwarg in order for your tests to have particular request attributes. #### get(view_cls, *args, **kwargs) Calls view_cls.get() method after instantiating view class. Renders view templates and sets context if appropriate. #### post(view_cls, *args, **kwargs) Calls view_cls.post() method after instantiating view class. Renders view templates and sets context if appropriate. #### get_response(request, view_func) Obtain response from view class method (typically get or post). No middleware is invoked, but templates are rendered and context saved if appropriate. #### get_check_200(url, *args, **kwargs) Test that we can GET a page and it returns a 200 #### assertGoodView(url_name, *args, **kwargs) Quick-n-dirty testing of a given view. Ensures view returns a 200 status and that generates less than 50 database queries. ### APITestCase Bases: [TestCase](./#test_plus.test.TestCase) ### NoPreviousResponse Bases: [Exception](https://docs.python.org/3/library/exceptions.html#Exception) ## test_plus.status_codes.StatusCodeAssertionMixin The following assert_http_###_status_name methods were intentionally added statically instead of dynamically so that code completion in IDEs like PyCharm would work. They are the preferred spelling, but the response_XXX methods are supported permanently and are not deprecated. The assert methods contain both the number and the status name slug so that people that remember them best by their numeric code and people that remember best by their name will be able to easily find the assertion they need. This was also directly patterned off of what the Django Rest Framework uses _. ### assert_http_100_continue(response=None, msg=None) Server has received request headers and client should proceed to send request body. ### assert_http_101_switching_protocols(response=None, msg=None) Server is switching protocols as requested by the client. ### assert_http_102_processing(response=None, msg=None) Server has received the request and is still processing it (WebDAV). ### assert_http_103_early_hints(response=None, msg=None) Used to return some response headers before final HTTP message. ### assert_http_200_ok(response=None, msg=None) Request succeeded. ### assert_http_201_created(response=None, msg=None) Request succeeded and a new resource was created. ### assert_http_202_accepted(response=None, msg=None) Request received but not yet acted upon. ### assert_http_203_non_authoritative_information(response=None, msg=None) Returned metadata is not from the origin server but from a third party. ### assert_http_204_no_content(response=None, msg=None) Request succeeded but no content to send back. ### assert_http_205_reset_content(response=None, msg=None) Request succeeded and client should reset the document view. ### assert_http_206_partial_content(response=None, msg=None) Request succeeded and body contains requested ranges of data. ### assert_http_207_multi_status(response=None, msg=None) Response conveys information about multiple resources (WebDAV). ### assert_http_208_already_reported(response=None, msg=None) Members of a DAV binding have already been enumerated (WebDAV). ### assert_http_226_im_used(response=None, msg=None) Server has fulfilled a request for the resource with instance manipulations applied. ### assert_http_300_multiple_choices(response=None, msg=None) Request has multiple possible responses. ### assert_http_301_moved_permanently(response=None, msg=None, url=None) URL of requested resource has been changed permanently. ### assert_http_302_found(response=None, msg=None, url=None) Resource temporarily located at a different URI. ### assert_http_303_see_other(response=None, msg=None) Server redirects to get the requested resource at another URI. ### assert_http_304_not_modified(response=None, msg=None) Response has not been modified, client can use cached version. ### assert_http_305_use_proxy(response=None, msg=None) Response must be accessed through a proxy (deprecated). ### assert_http_306_reserved(response=None, msg=None) No longer used, reserved for future use. ### assert_http_307_temporary_redirect(response=None, msg=None) Resource temporarily located at a different URI, method must not change. ### assert_http_308_permanent_redirect(response=None, msg=None) Resource permanently located at a different URI, method must not change. ### assert_http_400_bad_request(response=None, msg=None) Server cannot process request due to client error. ### assert_http_401_unauthorized(response=None, msg=None) Request requires user authentication. ### assert_http_402_payment_required(response=None, msg=None) Reserved for future use in digital payment systems. ### assert_http_403_forbidden(response=None, msg=None) Server refuses to authorize the request. ### assert_http_404_not_found(response=None, msg=None) Server cannot find the requested resource. ### assert_http_405_method_not_allowed(response=None, msg=None) Request method not supported for the requested resource. ### assert_http_406_not_acceptable(response=None, msg=None) Server cannot produce a response matching the Accept headers. ### assert_http_407_proxy_authentication_required(response=None, msg=None) Client must authenticate with the proxy. ### assert_http_408_request_timeout(response=None, msg=None) Server timed out waiting for the request. ### assert_http_409_conflict(response=None, msg=None) Request conflicts with current state of the server. ### assert_http_410_gone(response=None, msg=None) Requested resource is permanently unavailable. ### assert_http_411_length_required(response=None, msg=None) Server requires Content-Length header in the request. ### assert_http_412_precondition_failed(response=None, msg=None) Client's preconditions in headers are not met. ### assert_http_413_request_entity_too_large(response=None, msg=None) Request payload is larger than server is willing to process. ### assert_http_414_request_uri_too_long(response=None, msg=None) URI requested by client is longer than server can interpret. ### assert_http_415_unsupported_media_type(response=None, msg=None) Media format of request data is not supported. ### assert_http_416_requested_range_not_satisfiable(response=None, msg=None) Range specified in Range header cannot be fulfilled. ### assert_http_417_expectation_failed(response=None, msg=None) Expectation in Expect header cannot be met. ### assert_http_418_im_a_teapot(response=None, msg=None) Server refuses to brew coffee because it is a teapot. ### assert_http_421_misdirected_request(response=None, msg=None) Request was directed to a server unable to produce a response. ### assert_http_422_unprocessable_entity(response=None, msg=None) Request is well-formed but contains semantic errors. ### assert_http_423_locked(response=None, msg=None) Resource being accessed is locked (WebDAV). ### assert_http_424_failed_dependency(response=None, msg=None) Request failed due to failure of a previous request (WebDAV). ### assert_http_425_too_early(response=None, msg=None) Server is unwilling to risk processing a request that might be replayed. ### assert_http_426_upgrade_required(response=None, msg=None) Client should switch to a different protocol. ### assert_http_428_precondition_required(response=None, msg=None) Server requires request to be conditional. ### assert_http_429_too_many_requests(response=None, msg=None) Client has sent too many requests in a given time period. ### assert_http_431_request_header_fields_too_large(response=None, msg=None) Request header fields are too large. ### assert_http_451_unavailable_for_legal_reasons(response=None, msg=None) Resource is unavailable due to legal reasons. ### assert_http_500_internal_server_error(response=None, msg=None) Server encountered an unexpected condition. ### assert_http_501_not_implemented(response=None, msg=None) Server does not support the functionality required. ### assert_http_502_bad_gateway(response=None, msg=None) Server received an invalid response from upstream server. ### assert_http_503_service_unavailable(response=None, msg=None) Server is not ready to handle the request. ### assert_http_504_gateway_timeout(response=None, msg=None) Server did not receive timely response from upstream server. ### assert_http_505_http_version_not_supported(response=None, msg=None) HTTP version used in request is not supported by server. ### assert_http_506_variant_also_negotiates(response=None, msg=None) Server has an internal configuration error in content negotiation. ### assert_http_507_insufficient_storage(response=None, msg=None) Server is unable to store the representation needed to complete the request (WebDAV). ### assert_http_508_loop_detected(response=None, msg=None) Server detected an infinite loop while processing the request (WebDAV). ### assert_http_509_bandwidth_limit_exceeded(response=None, msg=None) Bandwidth limit has been exceeded (non-standard). ### assert_http_510_not_extended(response=None, msg=None) Further extensions to the request are required. ### assert_http_511_network_authentication_required(response=None, msg=None) Client needs to authenticate to gain network access. ## test_plus.runner.NoLoggingRunner Bases: [DiscoverRunner](https://docs.djangoproject.com/en/stable/_objects/topics/testing/advanced/#django.test.runner.DiscoverRunner) ### run_tests(test_labels, extra_tests=None, **kwargs)