Part 3: Testing Dexterity

Create simple Dexterity Type

Add Dexterity to the package (setup.py):

    install_requires=[
        'plone.api',
        'setuptools',
        'z3c.jbot',
        'plone.app.dexterity',
        'plone.app.portlets',

Make sure dexterity is installed together with the package:

<?xml version="1.0"?>
<metadata>
  <version>1000</version>
  <dependencies>
    <dependency>profile-plone.app.dexterity:default</dependency>
  </dependencies>
</metadata>

configure.zcml:

<includeDependencies package="." />

Create profiles/default/types directory:

$ mkdir profiles/default/types

Create Factory Type Information (FTI) for Task Type (profiles/default/types/Task.xml):

<?xml version="1.0"?>
<object name="Task" meta_type="Dexterity FTI" i18n:domain="plonetraining.testing"
   xmlns:i18n="http://xml.zope.org/namespaces/i18n">
  <property name="title" i18n:translate="">Task</property>
  <property name="description"
    i18n:translate=""></property>
  <property name="icon_expr">string:${portal_url}/++theme++plonetraining.testing/Task.png</property>
  <property name="factory">Task</property>
  <property name="add_view_expr">string:${folder_url}/++add++Task</property>
  <property name="link_target"></property>
  <property name="immediate_view">view</property>
  <property name="global_allow">True</property>
  <property name="filter_content_types">True</property>
  <property name="allowed_content_types">
  </property>
  <property name="allow_discussion">False</property>
  <property name="default_view">view</property>
  <property name="view_methods">
    <element value="view"/>
  </property>
  <property name="default_view_fallback">False</property>
  <property name="add_permission">cmf.AddPortalContent</property>
  <property name="klass">plone.dexterity.content.Item</property>
  <property name="behaviors">
    <element value="plone.app.content.interfaces.INameFromTitle"/>
  </property>
  <property name="schema">plonetraining.testing.interfaces.ITask</property>
  <property name="model_source"></property>
  <property name="model_file"></property>
  <alias from="(Default)" to="(dynamic view)"/>
  <alias from="edit" to="@@edit"/>
  <alias from="sharing" to="@@sharing"/>
  <alias from="view" to="(selected layout)"/>
  <action title="View" action_id="view" category="object" condition_expr=""
    description="" icon_expr="" link_target="" url_expr="string:${object_url}"
    visible="True">
    <permission value="View"/>
  </action>
  <action title="Edit" action_id="edit" category="object" condition_expr=""
    description="" icon_expr="" link_target=""
    url_expr="string:${object_url}/edit" visible="True">
    <permission value="Modify portal content"/>
  </action>
</object>

Include Task FTI in Generic Setup Profile (profiles/default/types.xml):

<?xml version="1.0"?>
<object name="portal_types" meta_type="Plone Types Tool">
  <object name="Task" meta_type="Dexterity FTI"/>
</object>

Interface

interfaces.py:

# -*- coding: utf-8 -*-
from plonetraining.testing import _
from zope import schema
from zope.interface import Interface
from zope.publisher.interfaces.browser import IDefaultBrowserLayer


class IPlonetrainingTestingLayer(IDefaultBrowserLayer):
    """Marker interface that defines a browser layer."""


class ITask(Interface):

    title = schema.TextLine(
        title=_(u"Title"),
        required=True,
    )

    description = schema.Text(
        title=_(u"Description"),
        required=False,
    )

Integration Test

tests/test_task.py:

# -*- coding: utf-8 -*-
from plone.app.testing import SITE_OWNER_NAME
from plone.app.testing import SITE_OWNER_PASSWORD
from plone.testing.z2 import Browser
from plone.app.testing import TEST_USER_ID
from zope.component import queryUtility
from zope.component import createObject
from plone.app.testing import setRoles
from plone.dexterity.interfaces import IDexterityFTI
from plonetraining.testing.testing import PLONETRAINING_TESTING_FUNCTIONAL_TESTING  # noqa
from plonetraining.testing.testing import PLONETRAINING_TESTING_INTEGRATION_TESTING  # noqa
from plonetraining.testing.interfaces import ITask

import unittest2 as unittest


class TaskIntegrationTest(unittest.TestCase):

    layer = PLONETRAINING_TESTING_INTEGRATION_TESTING

    def setUp(self):
        self.portal = self.layer['portal']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])

    def test_schema(self):
        fti = queryUtility(IDexterityFTI, name='Task')
        schema = fti.lookupSchema()
        self.assertEqual(ITask, schema)

    def test_fti(self):
        fti = queryUtility(IDexterityFTI, name='Task')
        self.assertTrue(fti)

    def test_factory(self):
        fti = queryUtility(IDexterityFTI, name='Task')
        factory = fti.factory
        task = createObject(factory)
        self.assertTrue(ITask.providedBy(task))

    def test_adding(self):
        self.portal.invokeFactory('Task', 'task')
        self.assertTrue(ITask.providedBy(self.portal.task))

Functional Test

tests/test_task.py:

# -*- coding: utf-8 -*-
from plone.app.testing import SITE_OWNER_NAME
from plone.app.testing import SITE_OWNER_PASSWORD
from plone.testing.z2 import Browser
from plone.app.testing import TEST_USER_ID
from zope.component import queryUtility
from zope.component import createObject
from plone.app.testing import setRoles
from plone.dexterity.interfaces import IDexterityFTI
from plonetraining.testing.testing import PLONETRAINING_TESTING_FUNCTIONAL_TESTING  # noqa
from plonetraining.testing.testing import PLONETRAINING_TESTING_INTEGRATION_TESTING  # noqa
from plonetraining.testing.interfaces import ITask

import unittest2 as unittest


class TaskFunctionalTest(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 %s:%s' % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD,)
        )

    def test_add_task(self):
        self.browser.open(self.portal_url + '/++add++Task')
        self.browser.getControl(name="form.widgets.title").value = \
            "My Task"
        self.browser.getControl(name="form.widgets.description")\
            .value = "This is my task"
        self.browser.getControl("Save").click()

        self.assertEqual(
            "My Task",
            self.portal['my-task'].title,
        )

    def test_view_task(self):
        setRoles(self.portal, TEST_USER_ID, ['Manager'])
        self.portal.invokeFactory(
            "Task",
            id="my-task",
            title="My Task",
        )

        import transaction
        transaction.commit()

        self.browser.open(self.portal_url + '/my-task')

        self.assertTrue('My Task' in self.browser.contents)

Robot Test

tests/robot/test_task.robot:

# ============================================================================
# EXAMPLE ROBOT TESTS
# ============================================================================
#
# Run this robot test stand-alone:
#
#  $ bin/test -s plonetraining.testing -t test_task.robot --all
#
# Run this robot test with robot server (which is faster):
#
# 1) Start robot server:
#
# $ bin/robot-server --reload-path src plonetraining.testing.testing.PLONETRAINING_TESTING_ACCEPTANCE_TESTING
#
# 2) Run robot tests:
#
# $ bin/robot src/plonetraining/testing/tests/robot/test_task.robot
#
# See the http://docs.plone.org for further details (search for robot
# framework).
#
# ============================================================================

*** Settings *****************************************************************

Resource  plone/app/robotframework/selenium.robot
Resource  plone/app/robotframework/keywords.robot

Library  Remote  ${PLONE_URL}/RobotRemote

Test Setup  Open test browser
Test Teardown  Close all browsers


*** Test Cases ***************************************************************

Scenario: As a site administrator I can add a Task
  Given a logged-in site administrator
    and an add task form
   When I type 'My Task' into the title field
    and I submit the form
   Then a task with the title 'My Task' has been created

Scenario: As a site administrator I can view a Task
  Given a logged-in site administrator
    and a task 'My Task'
   When I go to the task view
   Then I can see the task title 'My Task'


*** Keywords *****************************************************************

# --- Given ------------------------------------------------------------------

a logged-in site administrator
  Enable autologin as  Site Administrator

an add task form
  Go To  ${PLONE_URL}/++add++Task

a task 'My Task'
  Create content  type=Task  id=my-task  title=My Task


# --- WHEN -------------------------------------------------------------------

I type '${title}' into the title field
  Input Text  name=form.widgets.title  ${title}

I submit the form
  Click Button  Save

I go to the task view
  Go To  ${PLONE_URL}/my-task
  Wait until page contains  Site Map


# --- THEN -------------------------------------------------------------------

a task with the title '${title}' has been created
  Wait until page contains  Site Map
  Page should contain  ${title}
  Page should contain  Item created

I can see the task title '${title}'
  Wait until page contains  Site Map
  Page should contain  ${title}