Skip to content

Commit

Permalink
add APITestCase
Browse files Browse the repository at this point in the history
  • Loading branch information
fcurella committed Oct 18, 2017
1 parent cd4500d commit cbe95b5
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 4 deletions.
7 changes: 7 additions & 0 deletions .travis.yml
Expand Up @@ -9,6 +9,7 @@ install:
- pip install -e .
- pip install $DJANGO
- pip install -r requirements-dev.txt
- if [ -z ${DRF+x} ]; then echo "no drf"; else pip install djangorestframework; fi
script:
- flake8 . --ignore=E501,E402
- coverage run --source=test_plus setup.py test
Expand All @@ -24,6 +25,12 @@ env:
- DJANGO="Django<1.12"
- DJANGO="Django<2.1"

- DJANGO="Django<1.9" DRF=1
- DJANGO="Django<1.10" DRF=1
- DJANGO="Django<1.11" DRF=1
- DJANGO="Django<1.12" DRF=1
- DJANGO="Django<2.1" DRF=1

matrix:
exclude:
# Django>=2.0 doesn't support Python 2.7
Expand Down
26 changes: 26 additions & 0 deletions README.rst
Expand Up @@ -378,6 +378,32 @@ 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.response_200()

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

Testing class-based "generic" views
------------------------------------

Expand Down
26 changes: 26 additions & 0 deletions docs/usage.rst
Expand Up @@ -33,3 +33,29 @@ Note that you can also option to import it like this if you want, which is
more similar to the regular importing of Django's TestCase::

from test_plus import TestCase

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

Note that using ``APITestCase`` requires Django >= 1.8 and having installed ``django-rest-framework``.
3 changes: 2 additions & 1 deletion test_plus/__init__.py
@@ -1,5 +1,6 @@
from .test import TestCase
from .test import APITestCase, TestCase

__all__ = [
'APITestCase',
'TestCase',
]
10 changes: 10 additions & 0 deletions test_plus/compat.py
Expand Up @@ -2,3 +2,13 @@
from django.urls import reverse, NoReverseMatch
except ImportError:
from django.core.urlresolvers import reverse, NoReverseMatch # noqa

from django.core.exceptions import ImproperlyConfigured

try:
from rest_framework.test import APIClient
DRF = True
except ImportError:
def APIClient(*args, **kwargs):
raise ImproperlyConfigured('django-rest-framework must be installed in order to use APITestCase.')
DRF = False
6 changes: 5 additions & 1 deletion test_plus/test.py
Expand Up @@ -12,7 +12,7 @@
from django.test.client import store_rendered_templates
from django.utils.functional import curry

from .compat import reverse, NoReverseMatch
from .compat import reverse, NoReverseMatch, APIClient


class NoPreviousResponse(Exception):
Expand Down Expand Up @@ -374,6 +374,10 @@ def assertContext(self, key, value):
raise NoPreviousResponse("There isn't a previous response to query")


class APITestCase(TestCase):
client_class = APIClient


# Note this class inherits from TestCase defined above.
class CBVTestCase(TestCase):
"""
Expand Down
14 changes: 13 additions & 1 deletion test_project/test_app/tests.py
Expand Up @@ -16,8 +16,10 @@
from test_plus.test import (
CBVTestCase,
NoPreviousResponse,
TestCase
TestCase,
APITestCase,
)
from test_plus.compat import DRF

from .forms import TestNameForm
from .models import Data
Expand All @@ -28,6 +30,7 @@
)

DJANGO_16 = LooseVersion(django.get_version()) >= LooseVersion('1.6')
DJANGO_18 = LooseVersion(django.get_version()) >= LooseVersion('1.8')

if DJANGO_16:
from django.contrib.auth import get_user_model
Expand Down Expand Up @@ -556,3 +559,12 @@ def test_custom_method_with_value(self):
def test_custom_method_no_value(self):
instance = self.get_instance(CBView)
self.assertFalse(instance.special())


@unittest.skipUnless(DRF is True and DJANGO_18 is True, 'DRF is not installed.')
class TestAPITestCaseDRFInstalled(APITestCase):

def test_post(self):
data = {'testing': {'prop': 'value'}}
self.post('view-json', data=data, extra={'format': 'json'})
self.response_200()
3 changes: 2 additions & 1 deletion test_project/test_app/urls.py
Expand Up @@ -7,7 +7,7 @@
FormErrors, data_1, data_5, needs_login, view_200, view_201, view_301,
view_302, view_400, view_401, view_403, view_404, view_405, view_410,
view_contains, view_context_with, view_context_without, view_headers,
view_is_ajax, view_redirect,
view_is_ajax, view_json, view_redirect,
CBLoginRequiredView, CBView,
)

Expand All @@ -23,6 +23,7 @@
url(r'^view/404/$', view_404, name='view-404'),
url(r'^view/405/$', view_405, name='view-405'),
url(r'^view/410/$', view_410, name='view-410'),
url(r'^view/json/$', view_json, name='view-json'),
url(r'^view/redirect/$', view_redirect, name='view-redirect'),
url(r'^view/needs-login/$', needs_login, name='view-needs-login'),
url(r'^view/data1/$', data_1, name='view-data-1'),
Expand Down
11 changes: 11 additions & 0 deletions test_project/test_app/views.py
@@ -1,3 +1,5 @@
import json

from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseGone
Expand Down Expand Up @@ -55,6 +57,15 @@ def view_redirect(request):
return redirect('view-200')


def view_json(request):
ctype = request.META['CONTENT_TYPE']
if not ctype.startswith('application/json'):
raise ValueError("Request's content-type should be 'application/json'. Got '{}' instead.".format(ctype))

data = json.loads(request.body.decode('utf-8'))
return HttpResponse(json.dumps(data), content_type='application/json')


@login_required
def needs_login(request):
return HttpResponse('', status=200)
Expand Down
5 changes: 5 additions & 0 deletions tox.ini
Expand Up @@ -4,6 +4,10 @@ envlist =
py{34}-dj{15,16,17,18,19,110,111,20}
py{35}-dj{18,19,110,111,20}
py{36}-dj{111,20}
py{27}-dj{18,19,110,111}-drf
py{34}-dj{18,19,110,111,20}-drf
py{35}-dj{18,19,110,111,20}-drf
py{36}-dj{111,20}-drf

skip_missing_interpreters = True

Expand All @@ -23,6 +27,7 @@ deps =
dj110: Django<1.11
dj111: Django<2.0
dj20: Django<2.1
drf: djangorestframework
coverage
factory-boy

Expand Down

0 comments on commit cbe95b5

Please sign in to comment.