Integration test: add behavior to content type

Hi, in integration testing a behavior, how do i assign a behavior to a content type ?

I see

alsoProvides(self.request, IMyPackageLayer)

in the sample generated tests from plonecli. So could i do simply

alsoProvides(obj, IMyBehaviorMarker)

or

alsoProvides(obj, IMyBehavior)

to provide the behavior on the object or do i miss something ?

Is there a way to assign a behavior to a content type similar to adding it TTW under settings->dexterity content->->behaviors in integration tests ?

normally you should load your profile, in the type definition of your content type should be the behavior enable.

Sample: Folder.xml in profiles/default/types

<?xml version="1.0" encoding="utf-8"?>
<object xmlns:i18n="http://xml.zope.org/namespaces/i18n"
        meta_type="Dexterity FTI"
        name="Folder"
        i18n:domain="plone"
>
  <property name="behaviors"
            purge="False"
  >
    <element value="your.behavior" />
  </property>
</object>

class BehaviorIntegrationTest(unittest.TestCase):
    
    def setUp(self):
        self.app = self.layer["app"]
        self.portal = self.layer["portal"]
        setRoles(self.portal, TEST_USER_ID, ["Manager"])
        self.folder = api.content.create(self.portal, "Folder", "other-folder")
   
    def test_your_behavior_on_folder(self):
        self.assertTrue(IYourBehavior.providedBy(self.folder))

Here they suggests to add/remove behaviour in the FTI.

This may depend for your testing on whether you always intend to have that behavior assigned to that type?

  • If so, just make sure it is in the listed behaviors within the profile XML for your type.
  • If you want to test a behavior against a type without such a binding (e.g. against a type from another package, or a stock Plone type, without assigning it in the profile XML for that FTI), you should be able to add this to the FTI programmatically (traverse to it in portal_types, set attributes) as part of your test setUp/tearDown or in your layer construction.

Thank you all for the quick responses :slight_smile:

I want to make sure that a viewlet which displays behavior related content does not show up when a content type is viewed which does not have applied the behavior. When it is applied TTW (site-setup->dexterity content->Type-beahvior) the viewlet should be visible.

The following code is from the autogenerated viewlet-test:

    def test_signatur_viewlet_is_not_available_on_document(self):
        view = BrowserView(self.portal['other-document'], self.request)
        manager_name = 'plone.belowcontentbody'
        alsoProvides(self.request, IMyPackageLayer)
        manager = queryMultiAdapter(
            (self.portal['other-document'], self.request, view),
            IViewletManager,
            manager_name,
            default=None
        )
        self.assertIsNotNone(manager)
        manager.update()
        my_viewlet = [v for v in manager.viewlets if v.__name__ == 'signatur-viewlet']  # NOQA: E501                                        
        self.assertEqual(len(my_viewlet), 0)

In the test i want to assign the behavior temporarily to the instance object. When i do

    def test_signatur_viewlet_is_available_on_document(self):
        adapted = ISignaturVerhaltenMarker(self.portal['other-document'], None)
        view = BrowserView(adapted, self.request)
        manager_name = 'plone.belowcontentbody'
        alsoProvides(self.request, IMyPackageLayer)
        manager = queryMultiAdapter(
            (self.portal['other-document'], self.request, view),
            IViewletManager,
            manager_name,
            default=None
        )
        self.assertIsNotNone(manager)
        manager.update()
        my_viewlet = [v for v in manager.viewlets if v.__name__ == 'signatur-viewlet']  # NOQA: E501                                        
        #import pdb; pdb.set_trace()                                                                                                        
        self.assertEqual(len(my_viewlet), 1)

i get an assertion-error: AssertionError: 0 != 1, so the behavior is not enabled.

When i do

...
        manager = queryMultiAdapter(
            (adapted, self.request, view),
            IViewletManager,
            manager_name,
            default=None
        )
...

i get

  File "/opt/plone5.2/buildout-cache/eggs/cp38/zope.viewlet-4.2.1-py3.8.egg/zope/viewlet/manager.py", line 147, in update
    viewlets = self.filter(viewlets)
  File "/opt/plone5.2/buildout-cache/eggs/cp38/plone.app.viewletmanager-3.1.2-py3.8.egg/plone/app/viewletmanager/manager.py", line 49, in filter
    skinname = self.context.getCurrentSkinName()
AttributeError: 'NoneType' object has no attribute 'getCurrentSkinName'

Any clues ?

I test my custom viewlets via functional tests.

class DummyTestType:
  # subclass here
  _behaviors = []
  _portal_type = None

  def _setupFTI(self):
    fti = DexterityFTI(self._portal_type)
    self.portal.portal_types._setObject(self._portal_type, fti)
    fti.klass = "plone.dexterity.content.Item"
    fti.behaviors = self._behaviors

class BehaviorIntegrationTest(unittest.TestCase):
  layer = YOUR_INTEGRATION_TESTING_LAYER

  def setUp(self):
    """Custom shared utility setup for tests."""
    self.portal = self.layer["portal"]
    setRoles(self.portal, TEST_USER_ID, ["Manager"])

  def test_behavior(self):
    behavior = getUtility(IBehavior, "your.behaviorname")
    self.assertEqual(
      behavior.marker,
      ISignaturVerhaltenMarker,
    )

class ViewletFunctionalTest(DummyTestType, unittest.TestCase):
  
  layer = YOUR_FUNCTIONAL_TESTING_LAYER
  
  _portal_type = "DummyItem"
  _behaviors = ("your.behaviorname",)
  
  def _getBrowser(self):
    import transaction    
    transaction.commit()
    
    # Set up browser
    browser = Browser(self.layer["app"])
    browser.handleErrors = False
    browser.addHeader(
      "Authorization",
      "Basic {}:{}".format(
        TEST_USER_NAME,
        TEST_USER_PASSWORD,
      ),
    )
    return browser

  def setUp(self):  
    self.portal = self.layer["portal"]
    self.request = self.layer["request"]
    self.portal_url = self.portal.absolute_url()
    setRoles(self.portal, TEST_USER_ID, ["Manager"])
    
    self._setupFTI()
    
    # Dummy Item with Behavior
    self.portal.invokeFactory(self._portal_type, "dummy")
    
    # Document without Behavior
    self.portal.invokeFactory("Document", "doc")

  def test_viewlet_only_in_dummyview(self):
    from io import StringIO
    from lxml import etree

    browser = self._getBrowser()
    browser.open(self.portal.dummy.absolute_url())
    tree = etree.parse(StringIO(browser.contents), etree.HTMLParser())
    result = tree.xpath("//section[@id='section-custom-viewlet']")
    self.assertEqual(1, len(result), "No Viewlet found!")

  def test_viewlet_not_in_documentview(self):
    from io import StringIO
    from lxml import etree
    
    browser = self._getBrowser()
    browser.open(self.portal.doc.absolute_url())
    tree = etree.parse(StringIO(browser.contents), etree.HTMLParser())
    result = tree.xpath("//section[@id='section-custom-viewlet']")
    self.assertEqual(0, len(result), "Viewlet found, should not happen!")

shouldn't you use adapted instead of self.portal['other-document'] here? Otherwise the viewlet manager is not on the adapted content.

In the plone.app.dexterity they suggest to add the behaviour to the FTI, as @1letter does above.

Thanks a lot.

The missing part was:

class ViewletIntegrationTest(unittest.TestCase):

    layer = MY_PACKAGE_INTEGRATION_TESTING

    def setUp(self):
        ...
        fti = self.portal['portal_types']['Document']
        fti.behaviors = fti.behaviors + ('my.package.signatur_verhalten',)

to enable the behavior on the type.

Even creating a type without the behavior to test against is easy in a integration test:

from plone.dexterity.fti import DexterityFTI

class ViewletIntegrationTest(unittest.TestCase):

    layer = MY_PACKAGE_INTEGRATION_TESTING

    def setUp(self):
        ...
        # Erzeuge einen Typ ohne Verhalten.                                                                                                 
        fti = DexterityFTI('Inhaltstype')
        self.portal.portal_types._setObject('Inhaltstype', fti)
        fti.klass = "plone.dexterity.content.Item"
1 Like