Formalize URLPatternsTestCase (#5703)

* Add formalized URLPatternsTestCase

* Update versioning tests w/ new URLPatternsTestCase

* Cleanup router tests urlpatterns

* Add docs for URLPatternsTestCase
This commit is contained in:
Ryan P Kilby 2018-01-02 05:14:25 -05:00 committed by Carlton Gibson
parent 6b0bf72bb8
commit b65967711c
5 changed files with 138 additions and 55 deletions

View File

@ -292,7 +292,7 @@ similar way as with `RequestsClient`.
--- ---
# Test cases # API Test cases
REST framework includes the following test case classes, that mirror the existing Django test case classes, but use `APIClient` instead of Django's default `Client`. REST framework includes the following test case classes, that mirror the existing Django test case classes, but use `APIClient` instead of Django's default `Client`.
@ -324,6 +324,32 @@ You can use any of REST framework's test case classes as you would for the regul
--- ---
# URLPatternsTestCase
REST framework also provides a test case class for isolating `urlpatterns` on a per-class basis. Note that this inherits from Django's `SimpleTestCase`, and will most likely need to be mixed with another test case class.
## Example
from django.urls import include, path, reverse
from rest_framework.test import APITestCase, URLPatternsTestCase
class AccountTests(APITestCase, URLPatternsTestCase):
urlpatterns = [
path('api/', include('api.urls')),
]
def test_create_account(self):
"""
Ensure we can create a new account object.
"""
url = reverse('account-list')
response = self.client.get(url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
---
# Testing responses # Testing responses
## Checking the response data ## Checking the response data

View File

@ -5,11 +5,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import io import io
from importlib import import_module
from django.conf import settings from django.conf import settings
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.core.handlers.wsgi import WSGIHandler from django.core.handlers.wsgi import WSGIHandler
from django.test import testcases from django.test import override_settings, testcases
from django.test.client import Client as DjangoClient from django.test.client import Client as DjangoClient
from django.test.client import RequestFactory as DjangoRequestFactory from django.test.client import RequestFactory as DjangoRequestFactory
from django.test.client import ClientHandler from django.test.client import ClientHandler
@ -358,3 +359,44 @@ class APISimpleTestCase(testcases.SimpleTestCase):
class APILiveServerTestCase(testcases.LiveServerTestCase): class APILiveServerTestCase(testcases.LiveServerTestCase):
client_class = APIClient client_class = APIClient
class URLPatternsTestCase(testcases.SimpleTestCase):
"""
Isolate URL patterns on a per-TestCase basis. For example,
class ATestCase(URLPatternsTestCase):
urlpatterns = [...]
def test_something(self):
...
class AnotherTestCase(URLPatternsTestCase):
urlpatterns = [...]
def test_something_else(self):
...
"""
@classmethod
def setUpClass(cls):
# Get the module of the TestCase subclass
cls._module = import_module(cls.__module__)
cls._override = override_settings(ROOT_URLCONF=cls.__module__)
if hasattr(cls._module, 'urlpatterns'):
cls._module_urlpatterns = cls._module.urlpatterns
cls._module.urlpatterns = cls.urlpatterns
cls._override.enable()
super(URLPatternsTestCase, cls).setUpClass()
@classmethod
def tearDownClass(cls):
super(URLPatternsTestCase, cls).tearDownClass()
cls._override.disable()
if hasattr(cls, '_module_urlpatterns'):
cls._module.urlpatterns = cls._module_urlpatterns
else:
del cls._module.urlpatterns

View File

@ -14,7 +14,7 @@ from rest_framework.compat import get_regex_pattern
from rest_framework.decorators import detail_route, list_route from rest_framework.decorators import detail_route, list_route
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.routers import DefaultRouter, SimpleRouter from rest_framework.routers import DefaultRouter, SimpleRouter
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory, URLPatternsTestCase
from rest_framework.utils import json from rest_framework.utils import json
factory = APIRequestFactory() factory = APIRequestFactory()
@ -90,23 +90,10 @@ namespaced_router.register(r'example', MockViewSet, base_name='example')
empty_prefix_router = SimpleRouter() empty_prefix_router = SimpleRouter()
empty_prefix_router.register(r'', EmptyPrefixViewSet, base_name='empty_prefix') empty_prefix_router.register(r'', EmptyPrefixViewSet, base_name='empty_prefix')
empty_prefix_urls = [
url(r'^', include(empty_prefix_router.urls)),
]
regex_url_path_router = SimpleRouter() regex_url_path_router = SimpleRouter()
regex_url_path_router.register(r'', RegexUrlPathViewSet, base_name='regex') regex_url_path_router.register(r'', RegexUrlPathViewSet, base_name='regex')
urlpatterns = [
url(r'^non-namespaced/', include(namespaced_router.urls)),
url(r'^namespaced/', include((namespaced_router.urls, 'example'), namespace='example')),
url(r'^example/', include(notes_router.urls)),
url(r'^example2/', include(kwarged_notes_router.urls)),
url(r'^empty-prefix/', include(empty_prefix_urls)),
url(r'^regex/', include(regex_url_path_router.urls))
]
class BasicViewSet(viewsets.ViewSet): class BasicViewSet(viewsets.ViewSet):
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
@ -156,8 +143,12 @@ class TestSimpleRouter(TestCase):
assert route.mapping[method] == endpoint assert route.mapping[method] == endpoint
@override_settings(ROOT_URLCONF='tests.test_routers') class TestRootView(URLPatternsTestCase, TestCase):
class TestRootView(TestCase): urlpatterns = [
url(r'^non-namespaced/', include(namespaced_router.urls)),
url(r'^namespaced/', include((namespaced_router.urls, 'namespaced'), namespace='namespaced')),
]
def test_retrieve_namespaced_root(self): def test_retrieve_namespaced_root(self):
response = self.client.get('/namespaced/') response = self.client.get('/namespaced/')
assert response.data == {"example": "http://testserver/namespaced/example/"} assert response.data == {"example": "http://testserver/namespaced/example/"}
@ -167,11 +158,15 @@ class TestRootView(TestCase):
assert response.data == {"example": "http://testserver/non-namespaced/example/"} assert response.data == {"example": "http://testserver/non-namespaced/example/"}
@override_settings(ROOT_URLCONF='tests.test_routers') class TestCustomLookupFields(URLPatternsTestCase, TestCase):
class TestCustomLookupFields(TestCase):
""" """
Ensure that custom lookup fields are correctly routed. Ensure that custom lookup fields are correctly routed.
""" """
urlpatterns = [
url(r'^example/', include(notes_router.urls)),
url(r'^example2/', include(kwarged_notes_router.urls)),
]
def setUp(self): def setUp(self):
RouterTestModel.objects.create(uuid='123', text='foo bar') RouterTestModel.objects.create(uuid='123', text='foo bar')
RouterTestModel.objects.create(uuid='a b', text='baz qux') RouterTestModel.objects.create(uuid='a b', text='baz qux')
@ -219,12 +214,17 @@ class TestLookupValueRegex(TestCase):
@override_settings(ROOT_URLCONF='tests.test_routers') @override_settings(ROOT_URLCONF='tests.test_routers')
class TestLookupUrlKwargs(TestCase): class TestLookupUrlKwargs(URLPatternsTestCase, TestCase):
""" """
Ensure the router honors lookup_url_kwarg. Ensure the router honors lookup_url_kwarg.
Setup a deep lookup_field, but map it to a simple URL kwarg. Setup a deep lookup_field, but map it to a simple URL kwarg.
""" """
urlpatterns = [
url(r'^example/', include(notes_router.urls)),
url(r'^example2/', include(kwarged_notes_router.urls)),
]
def setUp(self): def setUp(self):
RouterTestModel.objects.create(uuid='123', text='foo bar') RouterTestModel.objects.create(uuid='123', text='foo bar')
@ -408,8 +408,11 @@ class TestDynamicListAndDetailRouter(TestCase):
self._test_list_and_detail_route_decorators(SubDynamicListAndDetailViewSet) self._test_list_and_detail_route_decorators(SubDynamicListAndDetailViewSet)
@override_settings(ROOT_URLCONF='tests.test_routers') class TestEmptyPrefix(URLPatternsTestCase, TestCase):
class TestEmptyPrefix(TestCase): urlpatterns = [
url(r'^empty-prefix/', include(empty_prefix_router.urls)),
]
def test_empty_prefix_list(self): def test_empty_prefix_list(self):
response = self.client.get('/empty-prefix/') response = self.client.get('/empty-prefix/')
assert response.status_code == 200 assert response.status_code == 200
@ -422,8 +425,11 @@ class TestEmptyPrefix(TestCase):
assert json.loads(response.content.decode('utf-8')) == {'uuid': '111', 'text': 'First'} assert json.loads(response.content.decode('utf-8')) == {'uuid': '111', 'text': 'First'}
@override_settings(ROOT_URLCONF='tests.test_routers') class TestRegexUrlPath(URLPatternsTestCase, TestCase):
class TestRegexUrlPath(TestCase): urlpatterns = [
url(r'^regex/', include(regex_url_path_router.urls)),
]
def test_regex_url_path_list(self): def test_regex_url_path_list(self):
kwarg = '1234' kwarg = '1234'
response = self.client.get('/regex/list/{}/'.format(kwarg)) response = self.client.get('/regex/list/{}/'.format(kwarg))
@ -438,8 +444,11 @@ class TestRegexUrlPath(TestCase):
assert json.loads(response.content.decode('utf-8')) == {'pk': pk, 'kwarg': kwarg} assert json.loads(response.content.decode('utf-8')) == {'pk': pk, 'kwarg': kwarg}
@override_settings(ROOT_URLCONF='tests.test_routers') class TestViewInitkwargs(URLPatternsTestCase, TestCase):
class TestViewInitkwargs(TestCase): urlpatterns = [
url(r'^example/', include(notes_router.urls)),
]
def test_suffix(self): def test_suffix(self):
match = resolve('/example/notes/') match = resolve('/example/notes/')
initkwargs = match.func.initkwargs initkwargs = match.func.initkwargs

View File

@ -12,7 +12,7 @@ from rest_framework import fields, serializers
from rest_framework.decorators import api_view from rest_framework.decorators import api_view
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.test import ( from rest_framework.test import (
APIClient, APIRequestFactory, force_authenticate APIClient, APIRequestFactory, URLPatternsTestCase, force_authenticate
) )
@ -283,3 +283,30 @@ class TestAPIRequestFactory(TestCase):
content_type='application/json', content_type='application/json',
) )
assert request.META['CONTENT_TYPE'] == 'application/json' assert request.META['CONTENT_TYPE'] == 'application/json'
class TestUrlPatternTestCase(URLPatternsTestCase):
urlpatterns = [
url(r'^$', view),
]
@classmethod
def setUpClass(cls):
assert urlpatterns is not cls.urlpatterns
super(TestUrlPatternTestCase, cls).setUpClass()
assert urlpatterns is cls.urlpatterns
@classmethod
def tearDownClass(cls):
assert urlpatterns is cls.urlpatterns
super(TestUrlPatternTestCase, cls).tearDownClass()
assert urlpatterns is not cls.urlpatterns
def test_urlpatterns(self):
assert self.client.get('/').status_code == 200
class TestExistingPatterns(TestCase):
def test_urlpatterns(self):
# sanity test to ensure that this test module does not have a '/' route
assert self.client.get('/').status_code == 404

View File

@ -7,33 +7,12 @@ from rest_framework.decorators import APIView
from rest_framework.relations import PKOnlyObject from rest_framework.relations import PKOnlyObject
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.reverse import reverse from rest_framework.reverse import reverse
from rest_framework.test import APIRequestFactory, APITestCase from rest_framework.test import (
APIRequestFactory, APITestCase, URLPatternsTestCase
)
from rest_framework.versioning import NamespaceVersioning from rest_framework.versioning import NamespaceVersioning
@override_settings(ROOT_URLCONF='tests.test_versioning')
class URLPatternsTestCase(APITestCase):
"""
Isolates URL patterns used during testing on the test class itself.
For example:
class MyTestCase(URLPatternsTestCase):
urlpatterns = [
...
]
def test_something(self):
...
"""
def setUp(self):
global urlpatterns
urlpatterns = self.urlpatterns
def tearDown(self):
global urlpatterns
urlpatterns = []
class RequestVersionView(APIView): class RequestVersionView(APIView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return Response({'version': request.version}) return Response({'version': request.version})
@ -163,7 +142,7 @@ class TestRequestVersion:
assert response.data == {'version': None} assert response.data == {'version': None}
class TestURLReversing(URLPatternsTestCase): class TestURLReversing(URLPatternsTestCase, APITestCase):
included = [ included = [
url(r'^namespaced/$', dummy_view, name='another'), url(r'^namespaced/$', dummy_view, name='another'),
url(r'^example/(?P<pk>\d+)/$', dummy_pk_view, name='example-detail') url(r'^example/(?P<pk>\d+)/$', dummy_pk_view, name='example-detail')
@ -329,7 +308,7 @@ class TestAllowedAndDefaultVersion:
assert response.data == {'version': 'v2'} assert response.data == {'version': 'v2'}
class TestHyperlinkedRelatedField(URLPatternsTestCase): class TestHyperlinkedRelatedField(URLPatternsTestCase, APITestCase):
included = [ included = [
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'), url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='namespaced'),
] ]
@ -361,7 +340,7 @@ class TestHyperlinkedRelatedField(URLPatternsTestCase):
self.field.to_internal_value('/v2/namespaced/3/') self.field.to_internal_value('/v2/namespaced/3/')
class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(URLPatternsTestCase): class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(URLPatternsTestCase, APITestCase):
nested = [ nested = [
url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='nested'), url(r'^namespaced/(?P<pk>\d+)/$', dummy_pk_view, name='nested'),
] ]