Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix some test isolation issues #64

Merged
merged 1 commit into from
Feb 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/61.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a test isolation issue that was preventing the MOCK_MAILHOST_FIXTURE to be used in multiple testcases [ale-rt]
1 change: 1 addition & 0 deletions news/62.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Properly configure the mail sender setting the appropriate registry records (Fixes #62)
14 changes: 9 additions & 5 deletions src/plone/app/testing/layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
from plone.app.testing.interfaces import TEST_USER_PASSWORD
from plone.app.testing.interfaces import TEST_USER_ROLES
from plone.app.testing.utils import MockMailHost
from plone.registry.interfaces import IRegistry
from plone.testing import Layer
from plone.testing import zca
from plone.testing import zodb
from plone.testing import zope
from plone.testing import zserver
from Products.MailHost.interfaces import IMailHost
from zope.component import getSiteManager
from zope.component import getUtility
from zope.component.hooks import setSite
from zope.event import notify
from zope.traversing.interfaces import BeforeTraverseEvent
Expand Down Expand Up @@ -382,19 +384,21 @@ class MockMailHostLayer(Layer):
"""
defaultBases = (PLONE_FIXTURE,)

def setUp(self):
def testSetUp(self):
with zope.zopeApp() as app:
portal = app[PLONE_SITE_ID]
portal.email_from_address = 'noreply@example.com'
portal.email_from_name = 'Plone Site'
registry = getUtility(IRegistry, context=portal)
if not registry["plone.email_from_address"]:
registry["plone.email_from_address"] = "noreply@example.com"
if not registry["plone.email_from_name"]:
registry["plone.email_from_name"] = u"Plone site"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a small reservation: this changes the registry, but does not undo this after test teardown.
But that was already the case for the properties on the portal, so I guess it is no big deal.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually want to do that!
Thanks anyway!

portal._original_MailHost = portal.MailHost
portal.MailHost = mailhost = MockMailHost('MailHost')
portal.MailHost.smtp_host = 'localhost'
sm = getSiteManager(context=portal)
sm.unregisterUtility(provided=IMailHost)
sm.registerUtility(mailhost, provided=IMailHost)

def tearDown(self):
def testTearDown(self):
with zope.zopeApp() as app:
portal = app[PLONE_SITE_ID]
_o_mailhost = getattr(portal, '_original_MailHost', None)
Expand Down
95 changes: 95 additions & 0 deletions src/plone/app/testing/layers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,98 @@ When the server is torn down, the ZServer thread is stopped.
Traceback (most recent call last):
...
requests.exceptions.ConnectionError: ...


Mock MailHost
~~~~~~~~~~~~~

The fixture ``MOCK_MAILHOST_FIXTURE`` layer
allows to replace the Zope MailHost with a dummy one.

**Note:** This layer builds on top of ``PLONE_FIXTURE``.
Like ``PLONE_FIXTURE``, it should only be used as a base layer,
and not directly in tests.
See this package's ``README`` file for details.

>>> layers.MOCK_MAILHOST_FIXTURE.__bases__
(<Layer 'plone.app.testing.layers.PloneFixture'>,)
>>> options = runner.get_options([], [])
>>> setupLayers = {}
>>> runner.setup_layer(options, layers.MOCK_MAILHOST_FIXTURE, setupLayers)
Set up plone.testing.zca.LayerCleanup in ... seconds.
Set up plone.testing.zope.Startup in ... seconds.
Set up plone.app.testing.layers.PloneFixture in ... seconds.
Set up plone.app.testing.layers.MockMailHostLayer in ... seconds.

Let's now simulate a test.
Test setup sets a couple of registry records and
replaces the mail host with a dummy one:

>>> from zope.component import getUtility
>>> from plone.registry.interfaces import IRegistry

>>> zca.LAYER_CLEANUP.testSetUp()
>>> zope.STARTUP.testSetUp()
>>> layers.MOCK_MAILHOST_FIXTURE.testSetUp()

>>> with helpers.ploneSite() as portal:
... registry = getUtility(IRegistry, context=portal)

>>> registry["plone.email_from_address"]
'noreply@example.com'
>>> registry["plone.email_from_name"]
'Plone site'

The dummy MailHost, instead of sending the emails,
stores them in a list of messages:

>>> with helpers.ploneSite() as portal:
... portal.MailHost.messages
[]

If we send a message, we can check it in the list:

>>> with helpers.ploneSite() as portal:
... portal.MailHost.send(
... "Hello world!",
... mto="foo@example.com",
... mfrom="bar@example.com",
... subject="Test",
... msg_type="text/plain",
... )
>>> with helpers.ploneSite() as portal:
... for message in portal.MailHost.messages:
... print(message)
MIME-Version: 1.0
Content-Type: text/plain
Subject: Test
To: foo@example.com
From: bar@example.com
Date: ...
<BLANKLINE>
Hello world!

The list can be reset:

>>> with helpers.ploneSite() as portal:
... portal.MailHost.reset()
... portal.MailHost.messages
[]

When the test is torn down the original MaiHost is restored:

>>> layers.MOCK_MAILHOST_FIXTURE.testTearDown()
>>> zope.STARTUP.testTearDown()
>>> zca.LAYER_CLEANUP.testTearDown()

>>> with helpers.ploneSite() as portal:
... portal.MailHost.messages
Traceback (most recent call last):
...
AttributeError: 'RequestContainer' object has no attribute 'messages'

>>> runner.tear_down_unneeded(options, [], setupLayers)
Tear down plone.app.testing.layers.MockMailHostLayer in ... seconds.
Tear down plone.app.testing.layers.PloneFixture in ... seconds.
Tear down plone.testing.zope.Startup in ... seconds.
Tear down plone.testing.zca.LayerCleanup in ... seconds.
33 changes: 27 additions & 6 deletions src/plone/app/testing/tests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import doctest
import re
import six
import unittest

Expand All @@ -12,18 +13,38 @@ def dummy(context):
pass


class Py23DocChecker(doctest.OutputChecker):
def check_output(self, want, got, optionflags):
if six.PY2:
got = re.sub("u'(.*?)'", "'\\1'", got)
return doctest.OutputChecker.check_output(self, want, got, optionflags)


def test_suite():
suite = unittest.TestSuite()
# seltest = doctest.DocFileSuite('selenium.rst', optionflags=OPTIONFLAGS)
# Run selenium tests on level 2, as it requires a correctly configured
# Firefox browser
# seltest.level = 2
suite.addTests([
doctest.DocFileSuite('cleanup.rst', optionflags=OPTIONFLAGS),
doctest.DocFileSuite('layers.rst', optionflags=OPTIONFLAGS),
doctest.DocFileSuite('helpers.rst', optionflags=OPTIONFLAGS),
# seltest,
])
suite.addTests(
[
doctest.DocFileSuite(
"cleanup.rst",
optionflags=OPTIONFLAGS,
checker=Py23DocChecker(),
),
doctest.DocFileSuite(
"layers.rst",
optionflags=OPTIONFLAGS,
checker=Py23DocChecker(),
),
doctest.DocFileSuite(
"helpers.rst",
optionflags=OPTIONFLAGS,
checker=Py23DocChecker(),
),
]
)
if six.PY2:
suite.addTests([
doctest.DocFileSuite(
Expand Down