Skip to content

Commit

Permalink
Add mock_interface and stub_interface methods to MockTestCase…
Browse files Browse the repository at this point in the history
…, creating a mock and using the interface as spec.
  • Loading branch information
jone committed Mar 13, 2012
1 parent 68b2a2e commit 6e0151e
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 1 deletion.
9 changes: 9 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,21 @@ The following additional methos are available:
``self.providing_mock(interfaces, *args, **kwargs)``
Creates a mock which provides ``interfaces``.

``self.mock_interface(interface, provides=None, *args, **kwargs)``
Creates a mock object implementing ``interface``. The mock does not
only provide ``interface``, but also use it as specification and
asserts that the mocked methods do exist on the interface.

``self.stub(*args, **kwargs)``
Creates a stub. It acts like a mock but has no assertions.

``self.providing_stub(interfaces, *args, **kwargs)``
Creates a stub which provides ``interfaces``.

``self.stub_interface(interface, provides=None, *args, **kwargs)``
Does the same as ``mock_interface``, but disables counting of expected
method calls and attribute access. See "Mocking vs. stubbing" below.

``self.set_parent(context, parent_context)``
Stubs the ``context`` so that its acquision parent is ``parent_context``.
Expects at least context to be a mock or a stub. Returns the ``context``.
Expand Down
4 changes: 4 additions & 0 deletions docs/HISTORY.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Changelog
1.2 (unreleased)
----------------

- Add ``mock_interface`` and ``stub_interface`` methods to MockTestCase, creating
a mock and using the interface as spec.
[jone]

- Accept also interfaces directly rather than lists of interfaces when
creating mocks or stubs which provides the interfaces.
[jone]
Expand Down
2 changes: 2 additions & 0 deletions ftw/testing/implementer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from zope.interface import Attribute
from zope.interface import classImplements
from zope.interface.interface import Method


Expand All @@ -13,6 +14,7 @@ def __call__(self):
impl = self._generate_class()
self._add_attributes(impl)
self._generate_methods(impl)
classImplements(impl, self.interface)
return impl

def _generate_class(self):
Expand Down
23 changes: 22 additions & 1 deletion ftw/testing/testcase.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from Acquisition import aq_inner, aq_parent
from ftw.testing.implementer import Implementer
from mocker import expect, ANY
from plone import mocktestcase
from zope.interface import Interface
from zope.interface import alsoProvides
from zope.interface import classImplements
from zope.interface import directlyProvides
import unittest2

Expand Down Expand Up @@ -32,6 +34,17 @@ def providing_mock(self, interfaces, *args, **kwargs):

return self.mocker.proxy(dummy, False, *args, **kwargs)

def mock_interface(self, interface, provides=None, *args, **kwargs):
"""Creates and returns a new mock object implementing `interface`.
The interface is used as "spec" - the test fails when an undefined
method is mocked or the method signature does not match the
interface.
"""
spec = Implementer(interface)()
if provides:
classImplements(spec, provides)
return self.mocker.mock(spec, *args, **kwargs)

def stub(self, *args, **kwargs):
"""Creates a stub object, which does not assert the applied
expectations.
Expand All @@ -45,6 +58,13 @@ def providing_stub(self, interfaces, *args, **kwargs):
kwargs['count'] = False
return self.providing_mock(interfaces, *args, **kwargs)

def stub_interface(self, interface, provides=None, *args, **kwargs):
"""Creates a stub object, implementing `interface` and using it
as spec.
"""
kwargs['count'] = False
return self.mock_interface(interface, provides=None, *args, **kwargs)

def set_parent(self, context, parent_context):
"""Set the acquisition parent of `context` to `parent_context`.
"""
Expand All @@ -70,7 +90,8 @@ def mock_tool(self, mock, name):
"""

if self._getToolByName_mock is None:
self._getToolByName_mock = self.mocker.replace('Products.CMFCore.utils.getToolByName')
self._getToolByName_mock = self.mocker.replace(
'Products.CMFCore.utils.getToolByName')

# patch: do not count.
self.expect(self._getToolByName_mock(ANY, name)).result(mock).count(0, None)
4 changes: 4 additions & 0 deletions ftw/testing/tests/test_implementer.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ def combined(self, foo, bar=True, *args, **kwargs):

class TestImplementer(TestCase):

def test_implements_interface(self):
generated = Implementer(IExample)()
self.assertTrue(IExample.implementedBy(generated))

def test_attributes(self):
generated = Implementer(IExample)()
self.assertTrue(hasattr(generated, 'foo'))
Expand Down
69 changes: 69 additions & 0 deletions ftw/testing/tests/test_testcase.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from Acquisition import aq_inner, aq_parent
from ftw.testing.testcase import MockTestCase
from unittest2 import TestResult
from zope.interface import Interface


class IFoo(Interface):
pass


class IBar(Interface):
pass

Expand Down Expand Up @@ -62,3 +64,70 @@ def test_assertRaises_from_unittest2(self):
# unittest2.TestCase.assertRaises has "with"-support
with self.assertRaises(Exception):
raise Exception()


class ILocking(Interface):

def lock():
pass

def unlock(all=False):
pass


class TestInterfaceMocking(MockTestCase):

def test_mock_interface_raises_when_function_not_known(self):
class Test(MockTestCase):
def runTest(self):
mock = self.mock_interface(ILocking)
self.expect(mock.crack()).result(True)
self.replay()
mock.crack()

result = TestResult()
Test().run(result=result)
self.assertFalse(result.wasSuccessful())
self.assertEqual(result.testsRun, 1)
self.assertEqual(len(result.failures), 1)
self.assertIn('mock.crack()\n - Method not ' + \
'found in real specification',
result.failures[0][1])

def test_mock_interface_raises_on_wrong_arguments(self):
class Test(MockTestCase):
def runTest(self):
mock = self.mock_interface(ILocking)
self.expect(mock.unlock(force=True)).result(True)
self.replay()
mock.unlock(force=True)

result = TestResult()
Test().run(result=result)
self.assertFalse(result.wasSuccessful())
self.assertEqual(result.testsRun, 1)
self.assertEqual(len(result.failures), 1)
self.assertIn('mock.unlock(force=True)\n - ' + \
'Specification is unlock(all=False): unknown kwargs: force',
result.failures[0][1])

def test_mock_interface_passes_when_defined_function_is_called(self):
mock = self.mock_interface(ILocking)
self.expect(mock.lock()).result('already locked')
self.replay()
self.assertEqual(mock.lock(), 'already locked')

def test_mock_interface_providing_addtional_interfaces(self):
mock = self.mock_interface(ILocking, provides=[IFoo, IBar])
self.replay()
self.assertTrue(ILocking.providedBy(mock))
self.assertTrue(IFoo.providedBy(mock))
self.assertTrue(IBar.providedBy(mock))

def test_stub_interface_does_not_count(self):
mock = self.stub_interface(ILocking)
self.expect(mock.lock()).result('already locked')
self.replay()
self.assertEqual(mock.lock(), 'already locked')
self.assertEqual(mock.lock(), 'already locked')
self.assertEqual(mock.lock(), 'already locked')

0 comments on commit 6e0151e

Please sign in to comment.