Testing a Dexterity content type#
The most common thing that we could develop in Plone are content types. Let's see how to test if a new content type works as expected.
Create a new content type#
With plonecli we can add features to our package using the command line.
For example, let's create a new TestingItem
content type:
cd plonetraining.testing
plonecli add content_type
With this command, plonecli will ask you some questions and will register a new content type in our package.
Note
To follow this training, you need to answer questions like this:
Content type name (Allowed: _ a-z A-Z and whitespace) [Todo Task]: TestingItem
Content type description:
Use XML Model [y]: n
Dexterity base class (Container/Item) [Container]: Item
Should the content type globally addable? [y]: y
Create a content type class [y]: y
Activate default behaviors? [y]: y
Test the content type#
If we now try to re-run the tests, we see that the number of tests has increased.
This is because plonecli also creates a basic test for this new content type in the tests/test_ct_testing_item.py
file:
class TestingItemIntegrationTest(unittest.TestCase):
layer = PLONETRAINING_TESTING_INTEGRATION_TESTING
def setUp(self):
"""Custom shared utility setup for tests."""
self.portal = self.layer['portal']
setRoles(self.portal, TEST_USER_ID, ['Manager'])
self.parent = self.portal
def test_ct_testing_item_schema(self):
fti = queryUtility(IDexterityFTI, name='TestingItem')
schema = fti.lookupSchema()
schema_name = portalTypeToSchemaName('TestingItem')
self.assertEqual(schema_name, schema.getName())
def test_ct_testing_item_fti(self):
fti = queryUtility(IDexterityFTI, name='TestingItem')
self.assertTrue(fti)
def test_ct_testing_item_factory(self):
fti = queryUtility(IDexterityFTI, name='TestingItem')
factory = fti.factory
obj = createObject(factory)
def test_ct_testing_item_adding(self):
setRoles(self.portal, TEST_USER_ID, ['Contributor'])
obj = api.content.create(
container=self.portal, type='TestingItem', id='testing_item',
)
parent = obj.__parent__
self.assertIn('testing_item', parent.objectIds())
# check that deleting the object works too
api.content.delete(obj=obj)
self.assertNotIn('testing_item', parent.objectIds())
def test_ct_testing_item_globally_addable(self):
setRoles(self.portal, TEST_USER_ID, ['Contributor'])
fti = queryUtility(IDexterityFTI, name='TestingItem')
self.assertTrue(
fti.global_allow, u'{0} is not globally addable!'.format(fti.id),
)
We can see some interesting things:
We are using the
PLONETRAINING_TESTING_INTEGRATION_TESTING
layer, because we don't need to write functional testsWe are using the
setUp
method to set some variables and add some roles to the testing user.
In these tests, we are testing some very basic things for our content type, for example if it's correctly registered with factory type information (FTI), if we can create an item of our content type and if a user with a specific role (Contributor) can add it.
Note
plone.app.testing gives us a default user for tests. We could import its username in a variable called TEST_USER_ID
and set different roles when needed.
By default, each test is run as that logged-in user. If we want to run tests as anonymous users or using a different user, we need to logout and login.
Exercise 1#
Change the permissions for our new content type and only allow Manager role to add items of this type.
Fix old tests.
Create a new one test to verify that the Contributor role can't add items of this type.
Functional test#
So far, we have written only ìntegration
tests because we didn't need to test browser integration or commit any transactions.
As we covered before, if we don't need to create functional tests, it's better to avoid them because they are slower than integration tests.
Note
It's always better to avoid transactions in tests because they invalidate the isolation between tests in the same test case.
However, we will create a functional
test to see how it works and how content type creation works in a browser.
Let's create a new test case in the same file.
First, we need to import some new dependencies:
from plone.app.testing import SITE_OWNER_NAME
from plone.app.testing import SITE_OWNER_PASSWORD
from plone.testing.z2 import Browser
from plonetraining.testing.testing import PLONETRAINING_TESTING_FUNCTIONAL_TESTING
Now we can create a new test case:
class TestingItemFunctionalTest(unittest.TestCase):
layer = PLONETRAINING_TESTING_FUNCTIONAL_TESTING
def setUp(self):
app = self.layer['app']
self.portal = self.layer['portal']
self.request = self.layer['request']
self.portal_url = self.portal.absolute_url()
# Set up browser
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_add_testing_item(self):
self.browser.open(self.portal_url + '/++add++TestingItem')
self.browser.getControl(name='form.widgets.IBasic.title').value = 'Foo'
self.browser.getControl('Save').click()
self.assertTrue(
'<h1 class="documentFirstHeading">Foo</h1>'
in self.browser.contents
)
self.assertEqual('Foo', self.portal['foo'].title)
def test_view_testing_item(self):
setRoles(self.portal, TEST_USER_ID, ['Manager'])
api.content.create(
type='TestingItem',
title='Bar',
description='This is a description',
container=self.portal,
)
import transaction
transaction.commit()
self.browser.open(self.portal_url + '/bar')
The first thing we see is the new layer used: PLONETRAINING_TESTING_FUNCTIONAL_TESTING
.
In setUp
we configure the test case to use the Zope web browser and we login as site owner.
In test_add_testing_item
we simulate the user interaction of creating a new content in the browser with following actions:
Open the url for adding a new TestingItem content
Find the title field and compile it
Click to Save button
Check that after saving it, we can see its title.
In test_view_testing_item
we are checking that, by accessing a content item directly using a URL, we can see its values.
Note
self.browser.contents
shows the HTML of the last visited page.