Overriding adapters in unittests

My package overrides the EventOccurrenceAccessor provided by plone.app.event

zcml registration in https://github.com/plone/plone.app.event/blob/1.2.7/plone/app/event/recurrence.zcml#L6

 <adapter factory=".recurrence.EventOccurrenceAccessor" />

and adapter defined in https://github.com/plone/plone.app.event/blob/1.2.7/plone/app/event/recurrence.py#L137

class EventOccurrenceAccessor(object):

    implements(IEventAccessor)
    adapts(IOccurrence)

    ...

plone.app.event [archetypes] is listed as install_requires and pulled in by z3c.autoinclude

on my development instance it is enought to simply subclass the original class
and register the new adapater via my.package/overrides.zcml


class ProxyEventOccurrenceAccessor(EventOccurrenceAccessor):
    """override default adapter to handle proxyevents differently"""

    @property
    def url(self):
        return 'foo'

overrides.zcml

<configure xmlns="http://namespaces.zope.org/zope">

  <!-- override plone.app.event.recurrence.EventOccurrenceAccessor -->
  <adapter factory=".adapters.ProxyEventOccurrenceAccessor" />

</configure>

in the unit-test setup overriding the adapter needs extra work as z3c.autoinclude is deactivated in unittests
and overrides.zcml is not loaded automatically.

so i load overrides.zcml explicitly in the layer setup (using xmlconfig.includeOverrides):

layer definition and test-class

class MyLayer(PloneSandboxLayer):

    # use ptc_fixture as base to make sure everything is setup properly to mimik
    # products.plonetestcase infrastructure
    defaultBases = (PTC_FIXTURE,)

    def setUpZope(self, app, configurationContext):
        # Configure ZCML
        xmlconfig.file('testing.zcml', my.package.tests,
                       context=configurationContext)

        # EXTRA WORK to load overrides, found on
        # https://blog.niteo.co/load-overrides-zcml-in-plone-app-testing/
        xmlconfig.includeOverrides(configurationContext,
                                   file='overrides.zcml',
                                   package=my.package)

        z2.installProduct(app, 'my.package')

MY_FIXTURE = MyLayer()

MY_INTEGRATION_TESTING = IntegrationTesting(
    bases=(MY_FIXTURE,),
    name="my.package:Integration")


class TestProxyEventListing(ProxyEventBase, unittest.TestCase):

    layer = MY_INTEGRATION_TESTING

    def test_adapter(self):
        acc = IEventAccessor(occurrence)
        # fails because module still is 'plone.app.event'
        self.assertTrue(acc.__module__.startswith('my.package'))

testing.zcml looks like this


<configure
  xmlns="http://namespaces.zope.org/zope">

  <!-- load dependencies manually for testing environment that elsewise get
  pulled in by z3c.autocinclude -->

  <include package="z3c.jbot" />

  <include package="my.types" />

  <include package="plone.app.event" />
  <include package="plone.rest" />
  <include package="Products.AddRemoveWidget" />
  <include package="Products.AutocompleteWidget" />
  <include package="Products.CMFPlacefulWorkflow" />
  <include package="Products.TextIndexNG3" />

</configure>

the overrides.zcml is loaded when the tests are run (if i use a wrong classname i get a ConfigurationError)

however, in unittests I still get the adapter defined in plone.app.event, whereas the custom version is used when running bin/instance

You didn't forget to add a layer in your test class?

class TestSetup(unittest.TestCase):                                             
    """Test that package is properly installed."""               
                                                 
    layer = MY_INTEGRATION_TESTING

Anyways, we always have this kind of problem in tests, since bin/instance and bin/test behave differently... https://github.com/plone/plone.app.testing/issues/27

thanks @idgserpro
yes, the layer is set in the TestCase (on my screen the box containing the code example needs to be scrolled down to show it) and the zcml.

i agree that the need (or at least option) to distinguish between setup with z3c.autoinclude for zope instances and without it in tests is sub-optimal and error-prone.
the proposal in https://github.com/plone/plone.app.testing/issues/27 to remove <includeDependencies package="." /> and add explicit dependencies to configure.zcml is ok but requires to adapt/change all dependencies too (not just your custom theme/policy/types packages). and this is not likely to happen anytime soon.

the problem i am trying to solve here is not that a required dependency is missing or not registered properly.
in fact the adapter provided by plone.app.event works fine.
it's just that the overrides.zcml i provided to register a custom adapter gets loaded (proven by adding a typo in overrides.zcml and getting an error when building up the test layer) but the provided adapter is not used in the test code.

@tisto, @mauritsvanrees or @jensens maybe you know the problem or have an idea how to solve it.

it’s just that the overrides.zcml i provided to register a custom adapter gets loaded (proven by adding a typo in overrides.zcml and getting an error when building up the test layer) but the provided adapter is not used in the test code.

Indeed. What I was trying to say is that since bin/instance is not the same as bin/test, these strange behaviors may happen from time to time.

If you can solve your problem, the provided solution would be a great addition do Plone documentation.

Maybe look at how other packages use includeOverrides. Or how they don't: I see that plone.app.multilingual in Plone 5.1 loads overrides.zcml in the tests in the same way as the configure.zcml. But plone.app.robotframework does use includeOverrides.

Or maybe explicitly specify the implements and adapts in your class. Or use @implementer and @adapter.

I think z2.installProduct is only needed for packages in the Products namespace, but I could be wrong, and it should not matter here.

I found this issue when coming across the same problem (not only am I trying to get my overrides.zcml to execute in tests, I am hilariously overriding the exact same adapter). All I needed to do was add an additional call to PloneSandboxLayer's loadZCML method inside setUpZope

class EventsSiteLayer(PloneSandboxLayer):
    defaultBases = (PLONE_APP_CONTENTTYPES_FIXTURE,)

    def setUpZope(self, app, configuration_context):
        self.loadZCML(package=ims.events)
        self.loadZCML(name='overrides.zcml', package=ims.events)

    def setUpPloneSite(self, portal):
        applyProfile(portal, 'ims.events:default')
1 Like