Skip to content

Commit

Permalink
Fix some test isolation issues
Browse files Browse the repository at this point in the history
Fix a test isolation issue that was preventing the MOCK_MAILHOST_FIXTURE
to be used in multiple testcases (Fixes #61),

Properly configure the mail sender setting the appropriate registry
records (Fixes #62),

Adds test coverage.
  • Loading branch information
ale-rt committed Feb 5, 2020
1 parent 0395ca8 commit e8ba5ff
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 11 deletions.
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"
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

0 comments on commit e8ba5ff

Please sign in to comment.