Testing a view
Contents
Testing a view#
Another base Plone feature that we can test is a View.
Create a new view#
Create a new view with plonecli:
cd plonetraining.testing
plonecli add view
Follow the prompts and create a new view with the TestingItemView
Python class and a matching template.
This new view will be automatically registered in our package.
Test the view#
plonecli
creates basic tests (in the test_view_testing_item_view.py
file).
Inspecting this file, we see something like this:
class ViewsIntegrationTest(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'])
api.content.create(self.portal, 'Folder', 'other-folder')
api.content.create(self.portal, 'Document', 'front-page')
def test_testing_item_view_is_registered(self):
view = getMultiAdapter(
(self.portal['other-folder'], self.portal.REQUEST),
name='testing-item-view'
)
self.assertTrue(view.__name__ == 'testing-item-view')
# self.assertTrue(
# 'Sample View' in view(),
# 'Sample View is not found in testing-item-view'
# )
def test_testing_item_view_not_matching_interface(self):
with self.assertRaises(ComponentLookupError):
getMultiAdapter(
(self.portal['front-page'], self.portal.REQUEST),
name='testing-item-view',
)
In setUp
we are creating sample content that we will use in tests.
The first test (test_testing_item_view_is_registered
) tries to call the view on a Folder and checks that everything works well and that we get the correct view.
The second test (test_testing_item_view_not_matching_interface
) tries to call the view on a non-folderish content item (a Document) and checks that this raises an Exception.
If we take a look at the configure.zcml file where the view is registered, we can see that the view is registered only for folderish types. We want to test that this registration is correct.
We can test several things about a view:
If it is available only for a certain type of object
If it renders as we expect, by calling the view in an Integration test, or using the browser in a Functional test
If its methods return what we expect, by calling methods directly from the view instance
Exercise 1#
We want to use this view only for our content type and not for all folderish ones (also because TestingItem isn't a folderish type).
Change the view registration to be available only for our type
Update tests to check that we can call it only on a TestingItem content
The view template prints a string that is returned from its class. Write a test that checks this string.
Solution
TestingItem objects implements the ITestingItem
interface, so we need to update the view registration like this:
<browser:page
name="testing-item-view"
for="plonetraining.testing.content.testing_item.ITestingItem"
class=".testing_item_view.TestingItemView"
template="testing_item_view.pt"
permission="zope2.View"
/>
Then our test_view_testing_item_view
will become like this:
def setUp(self):
self.portal = self.layer['portal']
self.request = self.layer['request']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
api.content.create(self.portal, 'Folder', 'other-folder')
api.content.create(self.portal, 'Document', 'front-page')
api.content.create(self.portal, 'TestingItem', 'foo')
def test_testing_item_view_is_registered(self):
view = getMultiAdapter(
(self.portal['foo'], self.portal.REQUEST), name='testing-item-view'
)
self.assertTrue(view.__name__ == 'testing-item-view')
# self.assertTrue(
# 'Sample View' in view(),
# 'Sample View is not found in testing-item-view'
# )
def test_testing_item_view_not_matching_interface(self):
with self.assertRaises(ComponentLookupError):
getMultiAdapter(
(self.portal['front-page'], self.portal.REQUEST),
name='testing-item-view',
)
getMultiAdapter(
(self.portal['other-folder'], self.portal.REQUEST),
name='testing-item-view',
)
def test_testing_item_view_show_text(self):
view = api.content.get_view(
name='testing-item-view',
context=self.portal['foo'],
request=self.request,
)
self.assertIn('A small message', view())
Note
plonecli uses getMultiAdapter
to obtain a view and we use this for consistency with these pre-created tests, but the preferred way of obtaining a view is via plone.api.
Exercise 2#
Add a method in the view that gets a parameter from the request (
message
) and returns it.Check the method in the integration test
Update the template to print the value from that method
Test that calling the method from the view returns what we expect
Write a functional test to test browser integration
Solution
First, we need to implement that method in our view class in the views/testing_item_view.py
file:
class TestingItemView(BrowserView):
# If you want to define a template here, please remove the template from
# the configure.zcml registration of this view.
# template = ViewPageTemplateFile('testing_item_view.pt')
def __call__(self):
# Implement your own actions:
self.msg = _(u'A small message')
return self.index()
def custom_msg(self):
return self.request.form.get('message', '')
Then we add this message into the template in views/testing_item_view.pt
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:metal="http://xml.zope.org/namespaces/metal"
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="plonetraining.testing"
metal:use-macro="context/main_template/macros/master">
<body>
<metal:block fill-slot="content-core">
<h2 i18n:translate="">Sample View</h2>
<p>This is the default message: ${view/msg}</p>
<p tal:define="custom_msg view/custom_msg"
tal:condition="custom_msg">This is the custom message: ${view/custom_msg}</p>
</metal:block>
</body>
</html>
And finally we want to test everything, so let's add an integration test for the method:
def test_testing_custom_msg_without_parameter(self):
view = api.content.get_view(
name='testing-item-view',
context=self.portal['foo'],
request=self.request,
)
self.assertEqual('', view.custom_msg())
def test_testing_custom_msg_with_parameter(self):
view = api.content.get_view(
name='testing-item-view',
context=self.portal['foo'],
request=self.request,
)
self.request.form['message'] = 'hello'
self.assertEqual('hello', view.custom_msg())
Now we can create a functional test:
from plone.app.testing import SITE_OWNER_NAME
from plone.app.testing import SITE_OWNER_PASSWORD
from plone.testing.z2 import Browser
from transaction import commit
class ViewsFunctionalTest(unittest.TestCase):
layer = PLONETRAINING_TESTING_FUNCTIONAL_TESTING
def setUp(self):
app = self.layer['app']
self.portal = self.layer['portal']
self.portal_url = self.portal.absolute_url()
setRoles(self.portal, TEST_USER_ID, ['Manager'])
api.content.create(self.portal, 'TestingItem', 'foo')
# needed to "see" this content in the browser
commit()
self.browser = Browser(app)
self.browser.handleErrors = False
self.browser.addHeader(
'Authorization',
'Basic {username}:{password}'.format(
username=SITE_OWNER_NAME,
password=SITE_OWNER_PASSWORD),
)
def test_view_without_parameter(self):
self.browser.open(self.portal_url + '/foo')
self.assertNotIn(
'<p>This is the default message: A small message</p>',
self.browser.contents,
)
# because it's not the default view
self.browser.open(self.portal_url + '/foo/testing-item-view')
self.assertIn(
'<p>This is the default message: A small message</p>',
self.browser.contents,
)
self.assertNotIn(
'This is the custom message',
self.browser.contents,
)
def test_view_with_parameter(self):
self.browser.open(self.portal_url + '/foo?message=hello')
self.assertNotIn(
'<p>This is the default message: A small message</p>',
self.browser.contents,
)
self.assertNotIn(
'This is the custom message',
self.browser.contents,
)
# because it's not the default view
self.browser.open(self.portal_url + '/foo/testing-item-view?message=hello')
self.assertIn(
'<p>This is the default message: A small message</p>',
self.browser.contents,
)
self.assertIn(
'<p>This is the custom message: hello</p>',