summaryrefslogtreecommitdiff
path: root/mocker.py
diff options
context:
space:
mode:
Diffstat (limited to 'mocker.py')
-rw-r--r--mocker.py92
1 files changed, 78 insertions, 14 deletions
diff --git a/mocker.py b/mocker.py
index 9304398..4e6cdd5 100644
--- a/mocker.py
+++ b/mocker.py
@@ -1,7 +1,35 @@
"""
-Copyright (c) 2007 Gustavo Niemeyer <gustavo@niemeyer.net>
-
-Graceful platform for test doubles in Python (mocks, stubs, fakes, and dummies).
+Mocker
+
+Graceful platform for test doubles in Python: mocks, stubs, fakes, and dummies.
+
+Copyright (c) 2007-2010, Gustavo Niemeyer <gustavo@niemeyer.net>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
import __builtin__
import tempfile
@@ -18,12 +46,12 @@ if sys.version_info < (2, 4):
from sets import Set as set # pragma: nocover
-__all__ = ["Mocker", "expect", "IS", "CONTAINS", "IN", "MATCH",
- "ANY", "ARGS", "KWARGS"]
+__all__ = ["Mocker", "Expect", "expect", "IS", "CONTAINS", "IN", "MATCH",
+ "ANY", "ARGS", "KWARGS", "MockerTestCase"]
__author__ = "Gustavo Niemeyer <gustavo@niemeyer.net>"
-__license__ = "PSF License"
+__license__ = "BSD"
__version__ = "0.10.1"
@@ -56,6 +84,8 @@ class expect(object):
"""
+ __mocker__ = None
+
def __init__(self, mock, attr=None):
self._mock = mock
self._attr = attr
@@ -64,10 +94,26 @@ class expect(object):
return self.__class__(self._mock, attr)
def __call__(self, *args, **kwargs):
- getattr(self._mock.__mocker__, self._attr)(*args, **kwargs)
+ mocker = self.__mocker__
+ if not mocker:
+ mocker = self._mock.__mocker__
+ getattr(mocker, self._attr)(*args, **kwargs)
return self
+def Expect(mocker):
+ """Create an expect() "function" using the given Mocker instance.
+
+ This helper allows defining an expect() "function" which works even
+ in trickier cases such as:
+
+ expect = Expect(mymocker)
+ expect(iter(mock)).generate([1, 2, 3])
+
+ """
+ return type("Expect", (expect,), {"__mocker__": mocker})
+
+
# --------------------------------------------------------------------
# Extensions to Python's unittest.
@@ -88,8 +134,6 @@ class MockerTestCase(unittest.TestCase):
a few additional helper methods.
"""
- expect = expect
-
def __init__(self, methodName="runTest"):
# So here is the trick: we take the real test method, wrap it on
# a function that do the job we have to do, and insert it in the
@@ -138,12 +182,22 @@ class MockerTestCase(unittest.TestCase):
self.run = run_wrapper
self.mocker = Mocker()
+ self.expect = Expect(self.mocker)
self.__cleanup_funcs = []
self.__cleanup_paths = []
super(MockerTestCase, self).__init__(methodName)
+ def __call__(self, *args, **kwargs):
+ # This is necessary for Python 2.3 only, because it didn't use run(),
+ # which is supported above.
+ try:
+ super(MockerTestCase, self).__call__(*args, **kwargs)
+ finally:
+ if sys.version_info < (2, 4):
+ self.__cleanup()
+
def __cleanup(self):
for path in self.__cleanup_paths:
if os.path.isfile(path):
@@ -307,10 +361,9 @@ class MockerTestCase(unittest.TestCase):
except excClass, e:
return e
else:
+ excName = excClass
if hasattr(excClass, "__name__"):
excName = excClass.__name__
- else:
- excName = str(excClass)
raise self.failureException(
"%s not raised (%r returned)" % (excName, result))
@@ -593,16 +646,19 @@ class MockerBase(object):
while import_stack:
module_path = ".".join(import_stack)
try:
- object = __import__(module_path, {}, {}, [""])
+ __import__(module_path)
except ImportError:
attr_stack.insert(0, import_stack.pop())
if not import_stack:
raise
continue
else:
+ object = sys.modules[module_path]
for attr in attr_stack:
object = getattr(object, attr)
break
+ if isinstance(object, types.UnboundMethodType):
+ object = object.im_func
if spec is True:
spec = object
if type is True:
@@ -1075,6 +1131,10 @@ class Mock(object):
if self.__mocker__.is_recording() or self.__mocker_type__ is None:
return type(self)
return self.__mocker_type__
+ if name == "__length_hint__":
+ # This is used by Python 2.6+ to optimize the allocation
+ # of arrays in certain cases. Pretend it doesn't exist.
+ raise AttributeError("No __length_hint__ here!")
return self.__mocker_act__("getattr", (name,))
def __setattr__(self, name, value):
@@ -1114,9 +1174,12 @@ class Mock(object):
def __nonzero__(self):
try:
- return self.__mocker_act__("nonzero")
+ result = self.__mocker_act__("nonzero")
except MatchError, e:
return True
+ if type(result) is Mock:
+ return True
+ return result
def __iter__(self):
# XXX On py3k, when next() becomes __next__(), we'll be able
@@ -2042,6 +2105,7 @@ class Patcher(Task):
try:
return unpatched(*action.args, **action.kwargs)
except AttributeError:
+ type, value, traceback = sys.exc_info()
if action.kind == "getattr":
# The normal behavior of Python is to try __getattribute__,
# and if it raises AttributeError, try __getattr__. We've
@@ -2053,7 +2117,7 @@ class Patcher(Task):
pass
else:
return __getattr__(*action.args, **action.kwargs)
- raise
+ raise type, value, traceback
class PatchedMethod(object):