Useful additions to Django's default TestCase

Related tags

Testing testing django
Overview

django-test-plus

Useful additions to Django's default TestCase from REVSYS

pypi build matrix demo

Rationale

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!

Support

Supports: Python 3.6, 3.7, 3.8, 3.9, and 3.10.

Supports Django Versions: 2.0, 2.1, 2.2, 3.0, 3.1. and 3.2.

Documentation

Full documentation is available at http://django-test-plus.readthedocs.org

Installation

$ pip install django-test-plus

Usage

To use django-test-plus, have your tests inherit from test_plus.test.TestCase rather than the normal django.test.TestCase::

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:

from test_plus.test import TestCase as PlusTestCase

class TestCase(PlusTestCase):
    pass

And then in your tests use:

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:

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:

def test_url_reverse(tp):
    expected_url = '/api/'
    reversed_url = tp.reverse('api')
    assert expected_url == reversed_url

The tp_api fixture will provide a TestCase that uses django-rest-framework's APIClient():

def test_url_reverse(tp_api):
    response = tp_api.client.post("myapi", format="json")
    assert response.status_code == 200

Methods

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:

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=True, *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.:

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:

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:

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:

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:

def test_testplus_get_query(self):
    self.get('search', data={'query': 'testing'})

Would GET /search/?query=testing.

post(url_name, data, follow=True, *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.:

def test_post_named_url(self):
    response = self.post('my-url-name', data={'coolness-factor': 11.0},
                         extra={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'})

NOTE Along with the frequently used get and post, we support all of the HTTP verbs such as put, patch, head, trace, options, and delete in the same fashion.

get_context(key)

Often you need to get things out of the template context:

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:

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:

def test_in_context(self):
    self.get('my-view-with-some-context')
    self.assertContext('some-key', 'expected value')

assert_http_###_<status_name>(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:

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:

def test_better_status(self):
    response = self.get('my-url-name')
    self.assert_http_200_ok(response)

Django-test-plus provides a majority of the status codes assertions for you. The status assertions can be found in their own mixin and should be searchable if you're using an IDE like pycharm. It should be noted that in previous versions, django-test-plus had assertion methods in the pattern of response_###(), which are still available but have since been deprecated. See below for a list of those methods.

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_###_<status_name> methods will use the last response. So you can do:

def test_status(self):
    self.get('my-url-name')
    self.assert_http_200_ok()

Which is a bit shorter.

The response_###() methods that are deprecated, but still available for use, 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_###_<status_name>() methods, these methods will use the last response if it's available.

get_check_200(url_name, *args, **kwargs)

GETing and checking views return status 200 is a common test. This method makes it more convenient::

def test_even_better_status(self):
    response = self.get_check_200('my-url-name')

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:

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 factory to create it or override this method on your own sub-class.

To use a Factory Boy factory, create your class like this::

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 '<app_name>.<perm name>' or '<app_name>.*'. For example:

user2 = self.make_user(perms=['myapp.create_widget', 'otherapp.*'])

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.

Example usage:

class MyFormTest(TestCase):

    self.post('my-url-name', data={})
    self.print_form_errors()

    # or

    resp = self.post('my-url-name', data={})
    self.print_form_errors(resp)

    # or

    form = MyForm(data={})
    self.print_form_errors(form)

Authentication Helpers

assertLoginRequired(url_name, *args, **kwargs)

This method helps you test that a given named URL requires authorization:

def test_auth(self):
    self.assertLoginRequired('my-restricted-url')
    self.assertLoginRequired('my-restricted-object', pk=12)
    self.assertLoginRequired('my-restricted-object', slug='something')

login context

Along with ensuring a view requires login and creating users, the next thing you end up doing is logging in as various users to test your restriction logic:

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:

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:

def test_restrictions(self):
    user1 = self.make_user('u1')

    with self.login(user1):
        response = self.get('my-protected-view')

Ensuring low query counts

assertNumQueriesLessThan(number) - context

Django provides 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:

def test_something_out(self):

    with self.assertNumQueriesLessThan(7):
        self.get('some-view-with-6-queries')

assertGoodView(url_name, *args, **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:

def test_better_than_nothing(self):
    response = self.assertGoodView('my-url-name')

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:

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:

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.assert_http_200_ok()

Note that using APITestCase requires Django >= 1.8 and having installed django-rest-framework.

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 your usage of a generic view class comprises an override of an existing method. Invoking the entire view and the Django request/response stack is a waste of time when you really want to call the overridden method directly and test the result.

CBVTestCase to the rescue!

As with TestCase above, have your tests inherit from test_plus.test.CBVTestCase rather than TestCase like so:

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:

from django.views import generic
from test_plus.test import CBVTestCase

class MyClass(generic.DetailView)

    def get_context_data(self, **kwargs):
        kwargs['answer'] = 42
        return kwargs

class MyTests(CBVTestCase):

    def test_context_data(self):
        my_view = self.get_instance(MyClass, {'object': some_object})
        context = my_view.get_context_data()
        self.assertEqual(context['answer'], 42)

get(cls, initkwargs=None, *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:

response = self.get(MyClass)
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().

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, data=None, initkwargs=None, *args, **kwargs)

Invokes cls.post() and returns the response, rendering template if possible. Builds on the CBVTestCase.get_instance() foundation.

Example:

response = self.post(MyClass, 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().

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().

Development

To work on django-test-plus itself, clone this repository and run the following commands:

$ pip install -r requirements.txt
$ pip install -e .

NOTE: You will also need to ensure that the test_project directory, located at the root of this repo, is in your virtualenv's path.

Keep in touch!

If you have a question about this project, please open a GitHub issue. If you love us and want to keep track of our goings-on, here's where you can find us online:

Comments
  • More intuitive http response assertions

    More intuitive http response assertions

    I've been thinking it would be better to make assert methods for http status codes instead of the current response_XXX pattern.

    Rational:

    • The camel case and assert prefix match the Python TestCase pattern.
    • Remembering a name instead of a number is easier.
    • It makes your code more human readable.

    For example we could add the following:

    class BaseTestCase(object):
    
        def assertHttpStatus(self, status_code: int, response: object = None):
            response = self._which_response(response)
            self.assertEqual(response.status_code, status_code)
    
        def assertHttpOk(self, response=None):
            self.assertHttpStatus(200, response)
    
        def assertHttpRedirects(self, response: object = None, url: str = None):
            self.assertHttpStatus(302, response)
            response = self._which_response(response)
            if url is not None:
                self.assertEqual(response.url, url)
    
        def asssertHttpForbidden(self, response: object = None):
            self.assertHttpStatus(403, response)
    
        def asssertHttpNotFound(self, response: object = None):
            self.assertHttpStatus(404, response)
    
        def asssertHttpBadRequest(self, response: object = None):
            self.assertHttpStatus(403, response)
    
    opened by epicserve 15
  • :fire: Drop Python 3.3 Support

    :fire: Drop Python 3.3 Support

    Since Python 3.3 is no longer maintained and it's usage is next to nothing, we might as well speed up our test suite by dropping support for it. See notes here for how small it's usage was a year+ ago: https://github.com/pypa/pip/issues/3796

    opened by jefftriplett 9
  • Something broke?

    Something broke?

    image

    I am having this issue when I build. Did something break recently? Because my last build at 3 days ago was fine using the same version of django-test-plus

    3 days ago

    image

    opened by simkimsia 7
  • :fire: Drop Django <1.8 support

    :fire: Drop Django <1.8 support

    In theory, nothing has changed which will prevent older versions from working internally. However, we want to set a reasonable standard for which versions to update for yet keep our testsuite running quickly.

    Fixes #62

    opened by jefftriplett 7
  • Add assertResponseContains & assertResponseNotContains methods

    Add assertResponseContains & assertResponseNotContains methods

    A couple of convenience methods to save some typing in test cases.

    def test_response_contains(self):
        self.get('hello-world')
        self.assertResponseContains('<p>Hello, World!</p>')
    
    def test_response_not_contains(self):
        self.get('hello-world')
        self.assertResponseNotContains('<p>Hello, Frank!</p>')
    

    Uses the private _which_response method and defaults html to True.

    opened by goodtune 7
  • How to use token authentication while testing REST API

    How to use token authentication while testing REST API

    I am using django rest framework's token authentication in my django project and I need to unit test my ModelViewSets but the default login method of the TestCase class in not doing the job. I'm using drf's TokenAuthentication.

    REST_FRAMEWORK = {
        # Use Django's standard `django.contrib.auth` permissions,
        # or allow read-only access for unauthenticated users.
        'DEFAULT_PERMISSION_CLASSES': [
            'rest_framework.permissions.IsAuthenticated',
        ],
        'DEFAULT_AUTHENTICATION_CLASSES': (
            'rest_framework.authentication.TokenAuthentication',
        )
    }
    
    opened by Darwesh27 7
  • pytest support

    pytest support

    I use pytest for testing, so I don't use Django's TestCase directly. pytest-django is a package that provides fixtures that wrap Django's TestCase.

    Would you be amenable to providing pytest fixtures for your helpers, like pytest-django does? If so I'd be happy to put together a pull request for you to look over.

    opened by ftobia 7
  • :tractor: Initial GitHub Actions integration

    :tractor: Initial GitHub Actions integration

    Fixes #162

    This pull request:

    • Adds GHA support (via Matrix)
    • Adds 3.10 support
    • Drops Travis
    • Drops 3.5 in testing since it's well past EOL
    • Drops tox
    • Updates setup.cfg to make optional requirements installable

    Could do:

    • Delete tox.ini, but I left it in case it's useful locally.

    Follow up:

    • Adding DRF Matrix support since that's opinionated. #168
    opened by jefftriplett 6
  • Introspect the USERNAME_FIELD on custom User models

    Introspect the USERNAME_FIELD on custom User models

    The library used to make the assumption that the USERNAME_FIELD was always going to be username however this is easily overloaded.

    This allows testing of the following form:

    class MyTest(TestCase):
    
        user_factory = MyUserFactory
    
        def test_some_feature(self):
            u1 = self.make_user()
            self.login(u1):
                self.assertTrue(True)
    

    Assuming custom User model, such as:

    class User(AbstractBaseUser, PermissionsMixin):
    
        email = EmailField(
            verbose_name='email address',
            max_length=255,
            unique=True,
        )
    
        USERNAME_FIELD = 'email'
    
    opened by goodtune 6
  • Django 3.0 support

    Django 3.0 support

    Thanks for this package. It has made testing django a lot easier.

    • OS version and name: Ubuntu 18.04
    • python version: 3.8
    • django version: 3.0
    • django-test-plus version: 1.3.1

    When I attempt to run my test suite, I receive this error

    ImportError: cannot import name 'curry' from 'django.utils.functional'
    

    I believe that Django 3.0 removes this due to the drop in Python 2 support.

    https://docs.djangoproject.com/en/3.0/releases/3.0/#removed-private-python-2-compatibility-apis

    To achieve Django 3.0 support, I believe this will have to be removed.

    opened by laactech 5
  • 1.1.0 breaks pytest

    1.1.0 breaks pytest

    Hi,

    after upgrading from 1.0.22 to 1.1.0, i get suddenly the following stack trace:

    (venv) user@host:~/project$ pytest 
    Traceback (most recent call last):
      File "/home/user/project/venv/bin/pytest", line 11, in <module>
        sys.exit(main())
      File "/home/user/project/venv/lib/python3.5/site-packages/_pytest/config/__init__.py", line 56, in main
        config = _prepareconfig(args, plugins)
      File "/home/user/project/venv/lib/python3.5/site-packages/_pytest/config/__init__.py", line 181, in _prepareconfig
        pluginmanager=pluginmanager, args=args
      File "/home/user/project/venv/lib/python3.5/site-packages/pluggy/__init__.py", line 617, in __call__
        return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
      File "/home/user/project/venv/lib/python3.5/site-packages/pluggy/__init__.py", line 222, in _hookexec
        return self._inner_hookexec(hook, methods, kwargs)
      File "/home/user/project/venv/lib/python3.5/site-packages/pluggy/__init__.py", line 216, in <lambda>
        firstresult=hook.spec_opts.get('firstresult'),
      File "/home/user/project/venv/lib/python3.5/site-packages/pluggy/callers.py", line 196, in _multicall
        gen.send(outcome)
      File "/home/user/project/venv/lib/python3.5/site-packages/_pytest/helpconfig.py", line 89, in pytest_cmdline_parse
        config = outcome.get_result()
      File "/home/user/project/venv/lib/python3.5/site-packages/pluggy/callers.py", line 76, in get_result
        raise ex[1].with_traceback(ex[2])
      File "/home/user/project/venv/lib/python3.5/site-packages/pluggy/callers.py", line 180, in _multicall
        res = hook_impl.function(*args)
      File "/home/user/project/venv/lib/python3.5/site-packages/_pytest/config/__init__.py", line 607, in pytest_cmdline_parse
        self.parse(args)
      File "/home/user/project/venv/lib/python3.5/site-packages/_pytest/config/__init__.py", line 772, in parse
        self._preparse(args, addopts=addopts)
      File "/home/user/project/venv/lib/python3.5/site-packages/_pytest/config/__init__.py", line 724, in _preparse
        self.pluginmanager.load_setuptools_entrypoints("pytest11")
      File "/home/user/project/venv/lib/python3.5/site-packages/pluggy/__init__.py", line 397, in load_setuptools_entrypoints
        plugin = ep.load()
      File "/home/user/project/venv/lib/python3.5/site-packages/pkg_resources/__init__.py", line 2291, in load
        return self.resolve()
      File "/home/user/project/venv/lib/python3.5/site-packages/pkg_resources/__init__.py", line 2297, in resolve
        module = __import__(self.module_name, fromlist=['__name__'], level=0)
      File "/home/user/project/venv/lib/python3.5/site-packages/test_plus/__init__.py", line 1, in <module>
        from .test import APITestCase, TestCase
      File "<frozen importlib._bootstrap>", line 969, in _find_and_load
      File "<frozen importlib._bootstrap>", line 958, in _find_and_load_unlocked
      File "<frozen importlib._bootstrap>", line 664, in _load_unlocked
      File "<frozen importlib._bootstrap>", line 634, in _load_backward_compatible
      File "/home/user/project/venv/lib/python3.5/site-packages/_pytest/assertion/rewrite.py", line 216, in load_module
        py.builtin.exec_(co, mod.__dict__)
      File "/home/user/project/venv/lib/python3.5/site-packages/test_plus/test.py", line 16, in <module>
        from .compat import reverse, NoReverseMatch, APIClient
      File "<frozen importlib._bootstrap>", line 969, in _find_and_load
      File "<frozen importlib._bootstrap>", line 958, in _find_and_load_unlocked
      File "<frozen importlib._bootstrap>", line 664, in _load_unlocked
      File "<frozen importlib._bootstrap>", line 634, in _load_backward_compatible
      File "/home/user/project/venv/lib/python3.5/site-packages/_pytest/assertion/rewrite.py", line 216, in load_module
        py.builtin.exec_(co, mod.__dict__)
      File "/home/user/project/venv/lib/python3.5/site-packages/test_plus/compat.py", line 7, in <module>
        from rest_framework.test import APIClient
      File "/home/user/project/venv/lib/python3.5/site-packages/rest_framework/test.py", line 21, in <module>
        from rest_framework.compat import coreapi, requests
      File "/home/user/project/venv/lib/python3.5/site-packages/rest_framework/compat.py", line 107, in <module>
        from django.contrib.postgres import fields as postgres_fields
      File "/home/user/project/venv/lib/python3.5/site-packages/django/contrib/postgres/fields/__init__.py", line 1, in <module>
        from .array import *  # NOQA
      File "/home/user/project/venv/lib/python3.5/site-packages/django/contrib/postgres/fields/array.py", line 3, in <module>
        from django.contrib.postgres import lookups
      File "/home/user/project/venv/lib/python3.5/site-packages/django/contrib/postgres/lookups.py", line 4, in <module>
        from .search import SearchVector, SearchVectorExact, SearchVectorField
      File "/home/user/project/venv/lib/python3.5/site-packages/django/contrib/postgres/search.py", line 47, in <module>
        class SearchVector(SearchVectorCombinable, Func):
      File "/home/user/project/venv/lib/python3.5/site-packages/django/contrib/postgres/search.py", line 50, in SearchVector
        _output_field = SearchVectorField()
      File "/home/user/project/venv/lib/python3.5/site-packages/django/db/models/fields/__init__.py", line 172, in __init__
        self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE
      File "/home/user/project/venv/lib/python3.5/site-packages/django/conf/__init__.py", line 56, in __getattr__
        self._setup(name)
      File "/home/user/project/venv/lib/python3.5/site-packages/django/conf/__init__.py", line 39, in _setup
        % (desc, ENVIRONMENT_VARIABLE))
    django.core.exceptions.ImproperlyConfigured: Requested setting DEFAULT_INDEX_TABLESPACE, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
    

    In case it is important, these are the requirements for the project:

    bleach==2.1.3
    dj-database-url==0.5.0
    Django==1.11.11
    django-admin-sortable2==0.6.19
    django-bleach==0.3.0
    django-classy-tags==0.8.0
    django-cms==3.5.2
    django-constance[database]==2.2.0
    django-cors-headers==2.2.0
    django-enumfields==0.10.1
    django-finalware==1.0.0
    django-filer==1.3.2
    django-formtools==2.1
    django-htmlmin==0.10.0
    django-nested-admin==3.0.21
    python-monkey-business==1.0.0
    django-ipware==2.1.0
    django-polymorphic==2.0.2
    django-reversion==2.0.13
    django-sass-processor==0.7
    django-sekizai==0.10.0
    Django-Select2==6.0.3
    django-settings-export==1.2.1
    django-simple-history==2.1.1
    django-sortedm2m==1.5.0
    django-treebeard==4.3
    djangocms-admin-style==1.2.8
    djangocms-attributes-field==0.3.0
    djangocms-cascade==0.16.2
    djangocms-column==1.7.0
    djangocms-inherit==0.2.2
    djangocms-link==2.1.2
    djangocms-picture==2.0.6
    djangocms-style==2.0.2
    djangocms-teaser==0.2.0
    djangocms-video==2.0.4
    djangorestframework==3.8.2
    html5lib==1.0.1
    iptools==0.6.1
    libsass==0.14.5
    Pillow==5.1.0
    psycopg2-binary==2.7.5
    pytz==2018.4
    six==1.11.0
    tzlocal==1.5.1
    enum34==1.1.6
    webencodings==0.5.1
    beautifulsoup4==4.6.0
    lxml==4.2.2
    elasticsearch==6.3.0
    elasticsearch-dsl==6.1.0
    
    django-compressor==2.2
    django-appconf==1.0.2
    rcssmin==1.0.6
    rjsmin==1.0.12
    
    brotlipy==0.7.0
    cffi==1.11.5
    django-brotli==0.1.3
    pycparser==2.18
    
    django-sslserver==0.20
    
    polib==1.1.0
    
    coverage==4.5.1
    cobertura-clover-transform==1.1.4.post1
    django-coverage-plugin==1.5.0
    django-test-plus==1.0.22
    factory-boy==2.11.1
    tblib==1.3.2
    
    flake8==3.5.0
    pytest==3.6.2
    pytest-django==3.3.2
    pytest-sugar==0.9.1
    pytest-cov==2.5.1
    pytest-xdist==1.22.2
    pytest-flake8==1.0.1
    pytest-html==1.19.0
    freezegun==0.3.10
    parameterized==0.6.1
    
    django-debug-toolbar==1.9.1
    ipython==6.4.0
    
    Sphinx==1.7.5
    sphinx-rtd-theme==0.4.0
    

    As you can notice in the stack trace, django-test-plus attempts to read the settings before they are actually loaded by pytest. Rolling back to 1.0.22 solves this issue for me.

    opened by lgandras 5
  • Fix CBVTest example code block indentation

    Fix CBVTest example code block indentation

    Two code block examples are not recognized as code blocks because they have two levels of indent:

    Screen Shot 2022-12-06 at 6 52 31 PM

    This PR should fix that formatting by removing one level of indent. Although I haven't seen the result rendered locally (just on github, "View file"), I'm following the same convention seen in the rest of DTP documentation, one level of indent.

    I didn't add the ReST code-block syntax since the existing DTP documentation does not use that format.

    ..  code-block:: php
    

    The result: Screen Shot 2022-12-06 at 7 04 48 PM

    opened by grahamu 0
  • Set custom UserFactory for tp pytest fixture

    Set custom UserFactory for tp pytest fixture

    Hi, y'all! I set up pytest on my Django project this evening. I was/am hoping to replace my use of the unittest-derived TestCase defined in this project with the tp fixture made available by pytest plugin.

    Judging by plugin.py, it looks like it's not possible to set a custom UserFactory for the fixture like you can for the TestCase.

    It would be nice to be able to define this for the tp fixture too. Without this ability, tp.make_user is limited to the User.objects.create_user call on https://github.com/revsys/django-test-plus/blob/master/test_plus/test.py#L266.

    One possible thought to fix this would be to expose a pytest configuration option that would set a module path to the UserFactory (e.g., myapp.factories.UserFactory) and load that class onto the t.user_factory attribute when the tp fixture creates the TestCase.

    Thanks for the great project. I really enjoy the extensions that test plus provides.

    opened by mblayman 0
  • Failing request with plain url produces excess backtraces

    Failing request with plain url produces excess backtraces

    BaseTestCase.request allows e.g. self.get() with a named url, or a plain url as a fallback where the method is called within exception handling for reverse().

            try:
                self.last_response = method(reverse(url_name, args=args, kwargs=kwargs), data=data, follow=follow, **extra)
            except NoReverseMatch:
                self.last_response = method(url_name, data=data, follow=follow, **extra)
    

    In the latter case, if the method fails, the exception chain will include less relevant traces:

    Traceback (most recent call last):
      File "[...]/site-packages/django/urls/base.py", line 75, in reverse
        extra, resolver = resolver.namespace_dict[ns]
    KeyError: 'https'
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      File "[...]/site-packages/test_plus/test.py", line 133, in request
        self.last_response = method(reverse(url_name, args=args, kwargs=kwargs), data=data, follow=follow, **extra)
      File "[...]/site-packages/django/urls/base.py", line 86, in reverse
        raise NoReverseMatch("%s is not a registered namespace" % key)
    django.urls.exceptions.NoReverseMatch: 'https' is not a registered namespace
    
    During handling of the above exception, another exception occurred:
    
    Traceback (most recent call last):
      [the actual test exception goes here]
    

    Probably it would be preferable to call the method outside exception handling for reverse(), i.e.

            try:
                url = reverse(url_name, args=args, kwargs=kwargs)
            except NoReverseMatch:
                if args or kwargs:
                    raise # unpopped (kw)args implies named url
                url = url_name
            self.last_response = method(url, data=data, follow=follow, **extra)
    

    😃 No reverse()-related traces in failing tests when plain url used 😢 NoReverseMatch lost if invalid named url used without (kw)args, will barf only in fallback instead 🤔 Plain url with discarded (kw)args will barf non-backwards-compleniently

    opened by ttolv 0
  • APITest case should default to JSON

    APITest case should default to JSON

    While I'm sure SOME people out there are still using XML for their APIs, the vast majority are JSON and we should just default the format option to all test calls to be JSON to ease the typing on DRF related testing.

    enhancement 
    opened by frankwiles 1
  • self.pos with attachments files

    self.pos with attachments files

    Hello, I am trying to test my . view

     def view(request):   
         request.FILES ..
    

    but i can`t make the correct request ,

    # normal test

     response = self.client.post(
         reverse('admin_file'),
         {'name': 'fred', 'file[]': get_test_image()},
         enctype='multipart/form-data'
     )
    

    #with test plus

    response = self.post(
        'admin_file',
        {'name': 'fred', 'file[]': get_test_image()},
        enctype='multipart/form-data'
    )
    Traceback (most recent call last):
      File "/code/app/tests/tests_views.py", line 159, in test_upload_file
        enctype='multipart/form-data'
      File "/usr/local/lib/python3.6/site-packages/test_plus/test.py", line 142, in post
        return self.request('post', url_name, *args, **kwargs)
      File "/usr/local/lib/python3.6/site-packages/test_plus/test.py", line 131, in request
        self.last_response = method(reverse(url_name, args=args, kwargs=kwargs), data=data, follow=follow, **extra)
      File "/usr/local/lib/python3.6/site-packages/django/urls/base.py", line 88, in reverse
        return iri_to_uri(resolver._reverse_with_prefix(view, prefix, *args, **kwargs))
      File "/usr/local/lib/python3.6/site-packages/django/urls/resolvers.py", line 562, in _reverse_with_prefix
        raise ValueError("Don't mix *args and **kwargs in call to reverse()!")
    ValueError: Don't mix *args and **kwargs in call to reverse()!
    

    #with reverse the same answer

    response = self.post(
        reverse('admin_file'),
        {'name': 'fred', 'file[]': get_test_image()},
        enctype='multipart/form-data'
    )
    ValueError: Don't mix *args and **kwargs in call to reverse()!
    

    #without reverse and file in data

    response = self.post(
       'admin_file',
        data={'name': 'fred', 'file[]': get_test_image()},
        enctype='multipart/form-data'
    )
    <HttpResponseNotFound status_code=404, "text/html; charset=utf-8">
    
    

    # with reverse works

    response = self.post(
        reverse('admin_file'),
        data={'name': 'fred', 'file[]': get_test_image()},
        enctype='multipart/form-data'
    )
    

    Then why does not work without reverse ?

    opened by Cubillosxy 0
  • Support ChannelTestCase

    Support ChannelTestCase

    In the hopefully near future, Django will have channels added. In which case, it would be handy if the test case for that system was supported by django-test-plus.

    opened by pydanny 0
Owner
REVSYS
Django, Python, ReactJS, PostgreSQL, Kubernetes and Ops. We offer technical support and consultation for complex open source systems.
REVSYS
a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly)

pytest-sugar pytest-sugar is a plugin for pytest that shows failures and errors instantly and shows a progress bar. Requirements You will need the fol

Teemu 963 Dec 28, 2022
Doing dirty (but extremely useful) things with equals.

Doing dirty (but extremely useful) things with equals. Documentation: dirty-equals.helpmanual.io Source Code: github.com/samuelcolvin/dirty-equals dir

Samuel Colvin 602 Jan 5, 2023
Useful additions to Django's default TestCase

django-test-plus Useful additions to Django's default TestCase from REVSYS Rationale Let's face it, writing tests isn't always fun. Part of the reason

REVSYS 546 Dec 22, 2022
mitm6 is a pentesting tool that exploits the default configuration of Windows to take over the default DNS server.

mitm6 is a pentesting tool that exploits the default configuration of Windows to take over the default DNS server.

Fox-IT 1.3k Jan 5, 2023
A testcase generation tool for Persistent Memory Programs.

PMFuzz PMFuzz is a testcase generation tool to generate high-value tests cases for PM testing tools (XFDetector, PMDebugger, PMTest and Pmemcheck) If

Systems Research at ShiftLab 14 Jul 24, 2022
A near-exact clone of google chrome's no internet game, or the "google dinosaur game", with some additions and extras.

dinoGame A near-exact clone of google chrome's no internet game, or the "google dinosaur game", with some additions and extras. Installation Download

null 1 Oct 26, 2021
Alacritty terminal used with Bash, Tmux, Vim, Mutt, Lynx, etc. and the many different additions added to each configuration file

Alacritty terminal used with Bash, Tmux, Vim, Mutt, Lynx, etc. and the many different additions added to each configuration file

Carter 19 Aug 24, 2022
SpotPlay2YouPlay - Converts new additions to a Spotify playlist to a matching Youtube playlist

SpotPlay2YouPlay - Converts new additions to a Spotify playlist to a matching Youtube playlist, can also be configured to converting whole playlists with the refresh fun

null 9 Mar 6, 2022
It is a useful project for developers that includes useful tools for Instagram

InstagramIG It is a useful project for developers that includes useful tools for Instagram Installation : pip install InstagramIG Logan Usage from In

Sidra ELEzz 14 Mar 14, 2022
A machine learning benchmark of in-the-wild distribution shifts, with data loaders, evaluators, and default models.

WILDS is a benchmark of in-the-wild distribution shifts spanning diverse data modalities and applications, from tumor identification to wildlife monitoring to poverty mapping.

P-Lambda 437 Dec 30, 2022
Get inside your stronghold and make all your Django views default login_required

Stronghold Get inside your stronghold and make all your Django views default login_required Stronghold is a very small and easy to use django app that

Mike Grouchy 384 Nov 23, 2022
django's default admin interface made customizable. popup windows replaced by modals. :mage: :zap:

django-admin-interface django-admin-interface is a modern responsive flat admin interface customizable by the admin itself. Features Beautiful default

Fabio Caccamo 1.3k Dec 31, 2022
Lightweight, configurable Sphinx theme. Now the Sphinx default!

What is Alabaster? Alabaster is a visually (c)lean, responsive, configurable theme for the Sphinx documentation system. It is Python 2+3 compatible. I

Jeff Forcier 670 Dec 19, 2022
a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly)

pytest-sugar pytest-sugar is a plugin for pytest that shows failures and errors instantly and shows a progress bar. Requirements You will need the fol

Teemu 963 Dec 28, 2022
A Chip-8 emulator written using Python's default libraries

Chippure A Chip-8 emulator written using Python's default libraries. Instructions: Simply launch the .py file and type the name of the Chip8 ROM you w

null 5 Sep 27, 2022
Replacement for the default Dark Sky Home Assistant integration using Pirate Weather

Pirate Weather Integrations This integration is designed to replace the default Dark Sky integration in Home Assistant with a slightly modified, but f

Alexander Rey 129 Jan 6, 2023
By default, networkx has problems with drawing self-loops in graphs.

By default, networkx has problems with drawing self-loops in graphs. It makes it hard to draw a graph with self-loops or to make a nicely looking chord diagram. This repository provides some code to draw self-loops nicely

Vladimir Shitov 5 Jan 6, 2022
Get inside your stronghold and make all your Django views default login_required

Stronghold Get inside your stronghold and make all your Django views default login_required Stronghold is a very small and easy to use django app that

Mike Grouchy 384 Nov 23, 2022
Command line parser for common log format (Nginx default).

Command line parser for common log format (Nginx default).

Lucian Marin 138 Dec 19, 2022
creates a batch file that uses adb to auto-install apks into the Windows Subsystem for Android and registers it as the default application to open apks.

wsa-apktool creates a batch file that uses adb to auto-install apks into the Windows Subsystem for Android and registers it as the default application

Aditya Vikram 3 Apr 5, 2022