Skip to content

Commit

Permalink
Merge 7f848e4 into 623b753
Browse files Browse the repository at this point in the history
  • Loading branch information
grahamu committed Jun 22, 2015
2 parents 623b753 + 7f848e4 commit 68c6b0f
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 4 deletions.
131 changes: 128 additions & 3 deletions test_plus/test.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import warnings
from distutils.version import LooseVersion

import django
from django.conf import settings
from django.core.urlresolvers import reverse, NoReverseMatch
from django.db import connections, DEFAULT_DB_ALIAS
from django.test import TestCase
from distutils.version import LooseVersion
from django.test import RequestFactory, signals, TestCase
from django.test.client import store_rendered_templates
from django.utils.functional import curry


class NoPreviousResponse(Exception):
Expand Down Expand Up @@ -241,4 +244,126 @@ def assertContext(self, key, value):
if self.last_response is not None:
self.assertEqual(self.last_response.context[key], value)
else:
raise NoPreviousResponse("There isn't a previous response to query")
raise NoPreviousResponse("There isn't a previous response to query")


# Note this class inherits from TestCase defined above.
class CBVTestCase(TestCase):
"""
Directly calls class-based generic view methods,
bypassing the Django test Client.
This process bypasses middleware invocation and URL resolvers.
Example usage:
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)
"""

def get_instance(self, cls, initkwargs=None, request=None, *args, **kwargs):
"""
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:
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().
"""
if initkwargs is None:
initkwargs = {}
instance = cls(**initkwargs)
instance.request = request
instance.args = args
instance.kwargs = kwargs
return instance

def get(self, cls, initkwargs=None, *args, **kwargs):
"""
Calls cls.get() method after instantiating view class
with `initkwargs`.
Renders view templates and sets context if appropriate.
"""
if initkwargs is None:
initkwargs = {}
request = RequestFactory().get('/')
instance = self.get_instance(cls, initkwargs=initkwargs, request=request, **kwargs)
self.last_response = self.get_response(request, instance.get)
self.context = self.last_response.context
return self.last_response

def post(self, cls, data={}, initkwargs=None, *args, **kwargs):
"""
Calls cls.post() method after instantiating view class
with `initkwargs`.
Renders view templates and sets context if appropriate.
"""
if initkwargs is None:
initkwargs = {}
request = RequestFactory().post('/', data)
instance = self.get_instance(cls, initkwargs=initkwargs, request=request, **kwargs)
self.last_response = self.get_response(request, instance.post)
self.context = self.last_response.context
return self.last_response

def get_response(self, 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.
"""
# Curry a data dictionary into an instance of
# the template renderer callback function.
data = {}
on_template_render = curry(store_rendered_templates, data)
signal_uid = "template-render-%s" % id(request)
signals.template_rendered.connect(on_template_render, dispatch_uid=signal_uid)
try:
response = view_func(request)

if hasattr(response, 'render') and callable(response.render):
response = response.render()
# Add any rendered template detail to the response.
response.templates = data.get("templates", [])
response.context = data.get("context")
else:
response.templates = None
response.context = None

return response
finally:
signals.template_rendered.disconnect(dispatch_uid=signal_uid)

def get_check_200(self, cls, initkwargs=None, *args, **kwargs):
""" Test that we can GET a page and it returns a 200 """
response = self.get(cls, initkwargs=initkwargs, *args, **kwargs)
self.response_200(response)
return response

def assertGoodView(self, cls, initkwargs=None, *args, **kwargs):
"""
Quick-n-dirty testing of a given view.
Ensures view returns a 200 status and that generates less than 50
database queries.
"""
query_count = kwargs.pop('test_query_count', 50)

with self.assertNumQueriesLessThan(query_count):
response = self.get(cls, initkwargs=initkwargs, *args, **kwargs)
self.response_200(response)
return response
1 change: 1 addition & 0 deletions test_project/test_app/templates/other.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>Another template</p>
1 change: 1 addition & 0 deletions test_project/test_app/templates/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>Hello world</p>
57 changes: 56 additions & 1 deletion test_project/test_app/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@

from distutils.version import LooseVersion

from test_plus.test import TestCase, NoPreviousResponse
from test_plus.test import (
CBVTestCase,
NoPreviousResponse,
TestCase
)

from .views import (
CBView,
CBTemplateView,
)

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

Expand Down Expand Up @@ -199,3 +208,49 @@ def test_post_is_ajax(self):
data={'item': 1},
extra={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'})
self.response_200(response)


class TestPlusCBViewTests(CBVTestCase):

def test_get(self):
response = self.get(CBView)
self.assertEqual(response.status_code, 200)

def test_post(self):
data = {'testing': True}
response = self.post(CBView, data=data)
self.assertEqual(response.status_code, 200)

def test_get_check_200(self):
self.get_check_200(CBView)

def test_assert_good_view(self):
self.assertGoodView(CBView)


class TestPlusCBTemplateViewTests(CBVTestCase):

def test_get(self):
response = self.get(CBTemplateView)
self.assertEqual(response.status_code, 200)
self.assertInContext('revsys')
self.assertContext('revsys', 42)
self.assertTemplateUsed(response, template_name='test.html')

def test_get_new_template(self):
template_name = 'other.html'
response = self.get(CBTemplateView, initkwargs={'template_name': template_name})
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, template_name=template_name)


class TestPlusCBCustomMethodTests(CBVTestCase):

def test_custom_method_with_value(self):
special_value = 42
instance = self.get_instance(CBView, {'special_value': special_value})
self.assertEqual(instance.special(), special_value)

def test_custom_method_no_value(self):
instance = self.get_instance(CBView)
self.assertFalse(instance.special())
29 changes: 29 additions & 0 deletions test_project/test_app/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import render
from django.views import generic

from .models import Data


# Function-based test views

def view_200(request):
return HttpResponse('', status=200)

Expand Down Expand Up @@ -54,3 +57,29 @@ def view_context_without(request):

def view_is_ajax(request):
return HttpResponse('', status=200 if request.is_ajax() else 404)


# Class-based test views

class CBView(generic.View):

def get(self, request):
return HttpResponse('', status=200)

def post(self, request):
return HttpResponse('', status=200)

def special(self):
if hasattr(self, 'special_value'):
return self.special_value
else:
return False


class CBTemplateView(generic.TemplateView):

template_name = 'test.html'

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

0 comments on commit 68c6b0f

Please sign in to comment.