summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfuzzyman <devnull@localhost>2010-01-02 17:05:34 +0000
committerfuzzyman <devnull@localhost>2010-01-02 17:05:34 +0000
commit9b8564ff1766559edbbc0fd381ade56bc9daf07d (patch)
treee514300dee615bc6d4e9f68a8c83dee6108c7843
parent34a9ea5b0343a2fd22a0cdd08d0c603849dc0213 (diff)
downloadmock-9b8564ff1766559edbbc0fd381ade56bc9daf07d.tar.gz
Improvements to extendmock and tests.
-rw-r--r--discover.py1
-rw-r--r--extendmock.py98
-rw-r--r--tests/testextendmock.py101
3 files changed, 172 insertions, 28 deletions
diff --git a/discover.py b/discover.py
index acfa7db..068b140 100644
--- a/discover.py
+++ b/discover.py
@@ -1,3 +1,4 @@
+#! /usr/bin/env python
# Copyright Michael Foord 2009
# Licensed under the BSD License
# See: http://pypi.python.org/pypi/discover
diff --git a/extendmock.py b/extendmock.py
index ca7ea0e..a9a8ea9 100644
--- a/extendmock.py
+++ b/extendmock.py
@@ -3,9 +3,52 @@ from mock import Mock
__all__ = ('mocksignature', 'MagicMock')
+"""
+extendmock adds additional features to the mock module.
+
+The mocksignature function creates wrapper functions that call a mock whilst
+preserving the signature of the original function.
+
+MagicMock is a class that adds magic method support to Mock. You add magic methods
+to mock instances in the same way you mock other attributes::
+
+ mock = Mock()
+ mock.__repr__ = lambda self: 'some string'
+
+Note that functions (or callable objects like a Mock instance) that are used for
+magic methods must take self as the first argument.
+
+The only unsupported magic methods (that I'm aware of) are:
+
+* Used by Mock: __init__, __new__, __getattr__, __setattr__, __delattr__
+* Rare: __dir__,
+* Can cause bad interactions with other functionality:
+
+ - __reversed__, __missing__, __del__, __unicode__, __getattribute__
+ - __get__, __set__, __delete__
+
+* Context manager methods are not directly supported (__enter__ and __exit__) as
+ Python doesn't lookup these methods on the class as it should (in Python 2.5 / 2.6
+ anyway).
+
+For more examples of how to use mocksignature and MagicMock see the tests.
+"""
+
+# getsignature and mocksignature heavily "inspired" by
+# the decorator module: http://pypi.python.org/pypi/decorator/
+# by Michele Simionato
+
+DELEGATE = MISSING = object()
+
def getsignature(func, handle_defaults=False):
assert inspect.ismethod(func) or inspect.isfunction(func)
regargs, varargs, varkwargs, defaults = inspect.getargspec(func)
+
+ # instance methods need to lose the self argument
+ im_self = getattr(func, 'im_self', None)
+ if im_self is not None:
+ regargs = regargs[1:]
+
argnames = list(regargs)
if varargs:
argnames.append(varargs)
@@ -32,41 +75,42 @@ def mocksignature(func, mock, handle_defaults=False):
class MagicMock(Mock):
pass
-MISSING = object()
-
-# Not supported:
-# Used by Mock: __init__, __new__, __getattr__, __setattr__, __delattr__
-# Rare: __dir__,
-# Can cause bad interactions with other functionality:
-# __reversed__, __missing__, __del__, __unicode__, __getattribute__
-# __get__, __set__, __delete__
-
-magic_methods = list((
- "eq ne lt le gt ge"
- "nonzero hash getitem setitem delitem"
- "len contains iter repr str"
- "divmod neg pos abs invert"
- "complex int long float oct hex index"
- "enter exit"
-).split())
+magic_methods = [
+ ("lt le gt ge eq ne", NotImplemented),
+ ("getitem setitem delitem", TypeError),
+ ("len contains iter", TypeError),
+ ("hash repr str", DELEGATE),
+ ("nonzero", True),
+ ("divmod neg pos abs invert", TypeError),
+ ("complex int long float oct hex index", TypeError)
+]
-numerics = "add sub mul div truediv floordiv mod lshift rshift and xor or".split()
-magic_methods.extend(numerics)
-magic_methods.extend('i%s' % m for m in numerics)
-magic_methods.extend('r%s' % m for m in numerics)
+numerics = "add sub mul div truediv floordiv mod lshift rshift and xor or"
+inplace = ' '.join('i%s' % n for n in numerics.split())
+right = ' '.join('r%s' % n for n in numerics.split())
+magic_methods.extend([
+ (numerics, NotImplemented), (inplace, NotImplemented), (right, NotImplemented)
+])
-def get_method(name):
+def get_method(name, action):
def func(self, *args, **kw):
- real = getattr(self, name, MISSING)
+ real = self.__dict__.get(name, MISSING)
if real is not MISSING:
return real(self, *args, **kw)
- return getattr(Mock, name)(self, *args, **kw)
+ if action is NotImplemented:
+ return NotImplemented
+ elif isinstance(action, type) and issubclass(action, Exception):
+ raise action
+ elif action is DELEGATE:
+ return getattr(Mock, name)(self, *args, **kw)
+ return action
func.__name__ = name
return func
-for method in magic_methods:
- name = '__%s__' % method
- setattr(MagicMock, name, get_method(name))
+for methods, action in magic_methods:
+ for method in methods.split():
+ name = '__%s__' % method
+ setattr(MagicMock, name, get_method(name, action))
\ No newline at end of file
diff --git a/tests/testextendmock.py b/tests/testextendmock.py
index 5b569b9..7307de7 100644
--- a/tests/testextendmock.py
+++ b/tests/testextendmock.py
@@ -1,6 +1,7 @@
# Copyright (C) 2007-20010 Michael Foord
# E-mail: fuzzyman AT voidspace DOT org DOT uk
# http://www.voidspace.org.uk/python/mock/
+from __future__ import with_statement
import os
import sys
@@ -48,8 +49,106 @@ class TestMockSignature(TestCase):
self.assertEquals(f.method('foo', 'bar'), 3)
mock.assert_called_with('foo', 'bar')
-
+class TestMagicMock(TestCase):
+
+ def testRepr(self):
+ mock = MagicMock()
+ self.assertEqual(repr(mock), object.__repr__(mock))
+ mock.__repr__ = lambda self: 'foo'
+ self.assertEqual(repr(mock), 'foo')
+
+
+ def testDictMethods(self):
+ mock = MagicMock()
+
+ self.assertRaises(TypeError, lambda: mock['foo'])
+ def _del():
+ del mock['foo']
+ def _set():
+ mock['foo'] = 3
+ self.assertRaises(TypeError, _del)
+ self.assertRaises(TypeError, _set)
+
+ _dict = {}
+ def getitem(s, name):
+ return _dict[name]
+ def setitem(s, name, value):
+ _dict[name] = value
+ def delitem(s, name):
+ del _dict[name]
+
+ mock.__setitem__ = setitem
+ mock.__getitem__ = getitem
+ mock.__delitem__ = delitem
+
+ self.assertRaises(KeyError, lambda: mock['foo'])
+ mock['foo'] = 'bar'
+ self.assertEquals(_dict, {'foo': 'bar'})
+ self.assertEquals(mock['foo'], 'bar')
+ del mock['foo']
+ self.assertEquals(_dict, {})
+
+
+ def testNumeric(self):
+ original = mock = MagicMock()
+ mock.value = 0
+
+ self.assertEqual(mock.__add__(3), NotImplemented)
+
+ def add(self, other):
+ mock.value += other
+ return self
+ mock.__add__ = add
+ self.assertEqual(mock + 3, mock)
+ self.assertEqual(mock.value, 3)
+
+ self.assertEqual(mock.__iadd__(3), NotImplemented)
+ mock.__iadd__ = add
+ mock += 6
+ self.assertEqual(mock, original)
+ self.assertEqual(mock.value, 9)
+
+ self.assertEqual(mock.__radd__(3), NotImplemented)
+ mock.__radd__ = add
+ self.assertEqual(7 + mock, mock)
+ self.assertEqual(mock.value, 16)
+
+ def testHash(self):
+ mock = MagicMock()
+ # test delegation
+ self.assertEqual(hash(mock), Mock.__hash__(mock))
+
+ def _hash(s):
+ return 3
+ mock.__hash__ = _hash
+ self.assertEqual(hash(mock), 3)
+
+ def testNonZero(self):
+ m = MagicMock()
+ self.assertTrue(bool(m))
+
+ nonzero = lambda s: False
+ m.__nonzero__ = nonzero
+ self.assertFalse(bool(m))
+
+ def testComparison(self):
+ self. assertEqual(MagicMock() < 3, object() < 3)
+ self. assertEqual(MagicMock() > 3, object() > 3)
+ self. assertEqual(MagicMock() <= 3, object() <= 3)
+ self. assertEqual(MagicMock() >= 3, object() >= 3)
+
+ mock = MagicMock()
+ def comp(s, o):
+ print 'foo'
+ return True
+ mock.__lt__ = mock.__gt__ = mock.__le__ = mock.__ge__ = comp
+ self. assertTrue(MagicMock() < 3)
+ self. assertTrue(MagicMock() > 3)
+ self. assertTrue(MagicMock() <= 3)
+ self. assertTrue(MagicMock() >= 3)
+
+
if __name__ == '__main__':
unittest.main()