Part 4: Testing Views

Test Simple View

class TaskViewIntegrationTest(unittest.TestCase):

    layer = PLONETRAINING_TESTING_INTEGRATION_TESTING

    def setUp(self):
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])
        self.portal.invokeFactory('Task', id='task', title='Task')
        self.task = self.portal.task

    def test_view_with_get_multi_adapter(self):
        # Get the view
        view = getMultiAdapter((self.task, self.request), name="view")
        # Put the view into the acquisition chain
        view = view.__of__(self.task)
        # Call the view
        self.assertTrue(view())

    def test_view_with_restricted_traverse(self):
        view = self.task.restrictedTraverse('view')
        self.assertTrue(view())

    def test_view_with_unrestricted_traverse(self):
        view = self.task.unrestrictedTraverse('view')
        self.assertTrue(view())

    def test_view_html_structure(self):
        import lxml
        view = getMultiAdapter((self.task, self.request), name="view")
        view = view.__of__(self.task)
        output = lxml.html.fromstring(view())
        self.assertEqual(1, len(output.xpath("/html/body/div")))

Implementation

browser/configure.zcml:

  <!-- Task View -->
  <browser:page
    name="view"
    for="plonetraining.testing.interfaces.ITask"
    class=".task.TaskView"
    permission="zope2.View"
    />

browser/task.py:

from zope.site.hooks import getSite
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile

import json


class TaskView(BrowserView):

    template = ViewPageTemplateFile('task.pt')

browser/task.pt:

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
      lang="en"
      metal:use-macro="here/prefs_main_template/macros/master"
      i18n:domain="plonetraining.testing">

<body>

<div metal:fill-slot="content-core">
  <h1 tal:content="context/title">Task</h1>
  <p tal:content="context/description|nothing">Task Description</p>
  <p>
    Request Term:
    <span tal:content="context/term|nothing">Request Term</span>
  </p>
</div>
</body>
</html>

Test View Browserlayer

class TaskViewWithBrowserlayerIntegrationTest(unittest.TestCase):

    layer = PLONETRAINING_TESTING_INTEGRATION_TESTING

    def setUp(self):
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])
        self.portal.invokeFactory('Task', id='task', title='Task')
        self.task = self.portal.task

    def test_view_with_browserlayer(self):
        # Make the request provide the browser layer so our view can be looked
        # up
        from zope.interface import directlyProvides
        from plonetraining.testing.interfaces import IPlonetrainingTestingLayer
        directlyProvides(self.request, IPlonetrainingTestingLayer)
        # Get the view
        view = getMultiAdapter(
            (self.task, self.request),
            name="view-with-browserlayer"
        )
        # Put the view into the acquisition chain
        view = view.__of__(self.task)
        # Call the view
        self.assertTrue(view())

Test View with Request Parameter

class TaskViewWithRequestParameterIntegrationTest(unittest.TestCase):

    layer = PLONETRAINING_TESTING_INTEGRATION_TESTING

    def setUp(self):
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])
        self.portal.invokeFactory('Task', id='task', title='Task')
        self.task = self.portal.task

    def test_view_with_request_parameter(self):
        self.request.set('term', 'foo')
        view = getMultiAdapter(
            (self.task, self.request),
            name="view-with-params"
        )
        view = view.__of__(self.task)
        self.failUnless(view())

Test Protected View

class TaskViewProtectedIntegrationTest(unittest.TestCase):

    layer = PLONETRAINING_TESTING_INTEGRATION_TESTING

    def setUp(self):
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])
        self.portal.invokeFactory('Task', id='task', title='Task')
        self.task = self.portal.task

    def test_view_protected(self):
        """Try to access a protected view and make sure we raise Unauthorized.
        """
        from AccessControl import Unauthorized
        logout()
        self.assertRaises(
            Unauthorized,
            self.task.restrictedTraverse,
            'view-protected'
        )

Test JSON View

class TaskViewJsonIntegrationTest(unittest.TestCase):

    layer = PLONETRAINING_TESTING_INTEGRATION_TESTING

    def setUp(self):
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])
        self.portal.invokeFactory('Task', id='task', title='Task')
        self.task = self.portal.task

    def test_view_json(self):
        view = getMultiAdapter(
            (self.task, self.request),
            name="view-json"
        )
        view = view.__of__(self.task)

        self.assertEqual(
            {
                u'title': u'Task',
                u'description': u''
            },
            json.loads(view())
        )
        self.assertEqual(
            'application/json; charset=utf-8',
            view.request.response.headers.get('content-type'),
        )

Test XML View

class TaskViewXmlIntegrationTest(unittest.TestCase):

    layer = PLONETRAINING_TESTING_INTEGRATION_TESTING

    def setUp(self):
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])
        self.portal.invokeFactory('Task', id='task', title='Task')
        self.task = self.portal.task

    def test_view_json(self):
        view = getMultiAdapter(
            (self.task, self.request),
            name="view-xml"
        )
        view = view.__of__(self.task)

        import lxml
        output = lxml.etree.fromstring(view())

        self.assertEqual(len(output.xpath("/task/title")), 1)
        self.assertEqual(output.xpath("/task/title")[0].text, u'Task')
        self.assertEqual(len(output.xpath("/task/description")), 1)
        self.assertEqual(output.xpath("/task/description")[0].text, None)
        self.assertEqual(
            'application/xml; charset=utf-8',
            view.request.response.headers.get('content-type')
        )

Test View Redirect

class TaskViewRedirectIntegrationTest(unittest.TestCase):

    layer = PLONETRAINING_TESTING_INTEGRATION_TESTING

    def setUp(self):
        self.portal = self.layer['portal']
        self.request = self.layer['request']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])
        self.portal.invokeFactory('Task', id='task', title='Task')
        self.task = self.portal.task

    def test_view_redirect(self):
        view = getMultiAdapter(
            (self.task, self.request),
            name="view-redirect"
        )
        view = view.__of__(self.task)

        view()

        self.assertEqual(
            'http://nohost/plone',
            self.request.response.headers['location']
        )

Troubleshooting

KeyError: ‘ACTUAL_URL’

Sometimes a view expect an ‘ACTUAL_URL’ param. If this is the case, make sure you provide the param in the test request:

def setUp(self):
    self.portal = self.layer['portal']
    self.request = self.layer['request']
    setRoles(self.portal, TEST_USER_ID, ['Manager'])
    self.portal.invokeFactory('Folder', 'test-folder')
    self.folder = self.portal['test-folder']
    self.request.set('URL', self.folder.absolute_url())
    self.request.set('ACTUAL_URL', self.folder.absolute_url())

def test_view(self):
    view = self.collection.restrictedTraverse('@@RSS')
    self.assertTrue(view())
    self.assertEquals(view.request.response.status, 200)

ComponentLookupError

If a view can not be looked up on a particular context, Plone will raise a ComponentLookupError (because views are multi-adapters), e.g.:

ComponentLookupError: ((<PloneSite at /plone>, <HTTPRequest, URL=http://nohost/plone>), <InterfaceClass zope.interface.Interface>, 'recipes')::

This can be solved for instance by providing a browser layer that has been missing:

def setUp(self):
    self.request = self.layer['request']
    from zope.interface import directlyProvides
    directlyProvides(self.request, IMyCompanyContenttypes)
    ...