Introduced class to which mocks can be bound in order to preserve binding information (Python3 and missing unbound methods issue).
This commit is contained in:
parent
ec173d58f0
commit
958cb59b11
89
mox.py
89
mox.py
|
@ -69,7 +69,6 @@ import inspect
|
|||
import re
|
||||
import types
|
||||
import unittest
|
||||
import collections
|
||||
|
||||
import stubout
|
||||
|
||||
|
@ -241,7 +240,7 @@ class Mox(object):
|
|||
self._mock_objects = []
|
||||
self.stubs = stubout.StubOutForTesting()
|
||||
|
||||
def CreateMock(self, class_to_mock, attrs=None):
|
||||
def CreateMock(self, class_to_mock, attrs=None, bounded_to=None):
|
||||
"""Create a new mock object.
|
||||
|
||||
Args:
|
||||
|
@ -249,13 +248,15 @@ class Mox(object):
|
|||
class_to_mock: class
|
||||
attrs: dict of attribute names to values that will be set on the mock
|
||||
object. Only public attributes may be set.
|
||||
bounded_to: optionally, when class_to_mock is not a class, it points
|
||||
to a real class object, to which attribute is bound
|
||||
|
||||
Returns:
|
||||
MockObject that can be used as the class_to_mock would be.
|
||||
"""
|
||||
if attrs is None:
|
||||
attrs = {}
|
||||
new_mock = MockObject(class_to_mock, attrs=attrs)
|
||||
new_mock = MockObject(class_to_mock, attrs=attrs, class_to_bind=bounded_to)
|
||||
self._mock_objects.append(new_mock)
|
||||
return new_mock
|
||||
|
||||
|
@ -305,18 +306,23 @@ class Mox(object):
|
|||
of the type of attribute.
|
||||
"""
|
||||
|
||||
if inspect.isclass(obj):
|
||||
class_to_bind = obj
|
||||
else:
|
||||
class_to_bind = None
|
||||
|
||||
attr_to_replace = getattr(obj, attr_name)
|
||||
attr_type = type(attr_to_replace)
|
||||
|
||||
if attr_type == MockAnything or attr_type == MockObject:
|
||||
raise TypeError('Cannot mock a MockAnything! Did you remember to '
|
||||
'call UnsetStubs in your previous test?')
|
||||
|
||||
|
||||
type_check = (
|
||||
attr_type in self._USE_MOCK_OBJECT or inspect.isclass(attr_to_replace)
|
||||
or isinstance(attr_to_replace, object))
|
||||
if type_check and not use_mock_anything:
|
||||
stub = self.CreateMock(attr_to_replace)
|
||||
stub = self.CreateMock(attr_to_replace, bounded_to=class_to_bind)
|
||||
else:
|
||||
stub = self.CreateMockAnything(description='Stub for %s' % attr_to_replace)
|
||||
stub.__name__ = attr_name
|
||||
|
@ -367,7 +373,7 @@ class Mox(object):
|
|||
if attr_type == MockAnything or attr_type == MockObject:
|
||||
raise TypeError('Cannot mock a MockAnything! Did you remember to '
|
||||
'call UnsetStubs in your previous test?')
|
||||
|
||||
|
||||
if not inspect.isclass(attr_to_replace):
|
||||
raise TypeError('Given attr is not a Class. Use StubOutWithMock.')
|
||||
|
||||
|
@ -380,6 +386,7 @@ class Mox(object):
|
|||
|
||||
self.stubs.UnsetAll()
|
||||
|
||||
|
||||
def Replay(*args):
|
||||
"""Put mocks into Replay mode.
|
||||
|
||||
|
@ -453,22 +460,24 @@ class MockAnything(object):
|
|||
return self.__class__.__dir__.__get__(self, self.__class__)
|
||||
|
||||
return self._CreateMockMethod(method_name)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self._CreateMockMethod('__str__')()
|
||||
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self._CreateMockMethod('__call__')(*args, **kwargs)
|
||||
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self._CreateMockMethod('__getitem__')(i)
|
||||
|
||||
def _CreateMockMethod(self, method_name, method_to_mock=None):
|
||||
|
||||
def _CreateMockMethod(self, method_name, method_to_mock=None,
|
||||
class_to_bind=object):
|
||||
"""Create a new mock method call and return it.
|
||||
|
||||
Args:
|
||||
# method_name: the name of the method being called.
|
||||
# method_to_mock: The actual method being mocked, used for introspection.
|
||||
# class_to_bind: Class to which method is bounded (object by default)
|
||||
method_name: str
|
||||
method_to_mock: a method object
|
||||
|
||||
|
@ -478,13 +487,14 @@ class MockAnything(object):
|
|||
|
||||
return MockMethod(method_name, self._expected_calls_queue,
|
||||
self._replay_mode, method_to_mock=method_to_mock,
|
||||
description=self._description)
|
||||
description=self._description,
|
||||
class_to_bind=class_to_bind)
|
||||
|
||||
def __nonzero__(self):
|
||||
"""Return 1 for nonzero so the mock can be used as a conditional."""
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
def __bool__(self):
|
||||
"""Return True for nonzero so the mock can be used as a conditional."""
|
||||
return True
|
||||
|
@ -537,7 +547,7 @@ class MockAnything(object):
|
|||
class MockObject(MockAnything):
|
||||
"""A mock object that simulates the public/protected interface of a class."""
|
||||
|
||||
def __init__(self, class_to_mock, attrs=None):
|
||||
def __init__(self, class_to_mock, attrs=None, class_to_bind=None):
|
||||
"""Initialize a mock object.
|
||||
|
||||
This determines the methods and properties of the class and stores them.
|
||||
|
@ -547,6 +557,8 @@ class MockObject(MockAnything):
|
|||
class_to_mock: class
|
||||
attrs: dict of attribute names to values that will be set on the mock
|
||||
object. Only public attributes may be set.
|
||||
class_to_bind: optionally, when class_to_mock is not a class at all, it
|
||||
points to a real class
|
||||
|
||||
Raises:
|
||||
PrivateAttributeError: if a supplied attribute is not public.
|
||||
|
@ -563,6 +575,12 @@ class MockObject(MockAnything):
|
|||
self._known_methods = set()
|
||||
self._known_vars = set()
|
||||
self._class_to_mock = class_to_mock
|
||||
|
||||
if inspect.isclass(self._class_to_mock):
|
||||
self._class_to_bind = self._class_to_mock
|
||||
else:
|
||||
self._class_to_bind = class_to_bind
|
||||
|
||||
try:
|
||||
if inspect.isclass(self._class_to_mock):
|
||||
self._description = class_to_mock.__name__
|
||||
|
@ -591,6 +609,11 @@ class MockObject(MockAnything):
|
|||
else:
|
||||
setattr(self, attr, value)
|
||||
|
||||
def _CreateMockMethod(self, *args, **kwargs):
|
||||
"""Overridden to provide self._class_to_mock to class_to_bind parameter."""
|
||||
kwargs.setdefault("class_to_bind", self._class_to_bind)
|
||||
return super(MockObject, self)._CreateMockMethod(*args, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Intercept attribute request on this object.
|
||||
|
||||
|
@ -776,7 +799,7 @@ class MockObject(MockAnything):
|
|||
# __call__ method
|
||||
method = None
|
||||
if type(self._class_to_mock) in (types.FunctionType, types.MethodType):
|
||||
method = self._class_to_mock;
|
||||
method = self._class_to_mock
|
||||
else:
|
||||
method = getattr(self._class_to_mock, '__call__')
|
||||
mock_method = self._CreateMockMethod('__call__', method_to_mock=method)
|
||||
|
@ -848,12 +871,15 @@ class MethodSignatureChecker(object):
|
|||
|
||||
_NEEDED, _DEFAULT, _GIVEN = range(3)
|
||||
|
||||
def __init__(self, method):
|
||||
def __init__(self, method, class_to_bind=None):
|
||||
"""Creates a checker.
|
||||
|
||||
Args:
|
||||
# method: A method to check.
|
||||
# class_to_bind: optionally, a class used to type check first method
|
||||
# parameter, only used with unbound methods
|
||||
method: function
|
||||
class_to_bind: type or None
|
||||
|
||||
Raises:
|
||||
ValueError: method could not be inspected, so checks aren't possible.
|
||||
|
@ -864,10 +890,17 @@ class MethodSignatureChecker(object):
|
|||
except TypeError:
|
||||
raise ValueError('Could not get argument specification for %r'
|
||||
% (method,))
|
||||
if inspect.ismethod(method):
|
||||
if inspect.ismethod(method) or class_to_bind:
|
||||
self._args = self._args[1:] # Skip 'self'.
|
||||
self._method = method
|
||||
self._instance = None # May contain the instance this is bound to.
|
||||
self._instance = getattr(method, "__self__", None)
|
||||
|
||||
# _bounded_to determines whether the method is bound or not
|
||||
if self._instance:
|
||||
self._bounded_to = self._instance.__class__
|
||||
else:
|
||||
self._bounded_to = class_to_bind or getattr(method, "im_class", None)
|
||||
|
||||
self._has_varargs = varargs is not None
|
||||
self._has_varkw = varkw is not None
|
||||
|
@ -920,10 +953,10 @@ class MethodSignatureChecker(object):
|
|||
#
|
||||
# NOTE: If a Func() comparator is used, and the signature is not
|
||||
# correct, this will cause extra executions of the function.
|
||||
if inspect.ismethod(self._method):
|
||||
if inspect.ismethod(self._method) or self._bounded_to:
|
||||
# The extra param accounts for the bound instance.
|
||||
if len(params) > len(self._required_args):
|
||||
expected = getattr(self._method, 'im_class', None)
|
||||
expected = self._bounded_to
|
||||
|
||||
# Check if the param is an instance of the expected class,
|
||||
# or check equality (useful for checking Comparators).
|
||||
|
@ -934,7 +967,7 @@ class MethodSignatureChecker(object):
|
|||
try:
|
||||
param_equality = (params[0] == expected)
|
||||
except:
|
||||
param_equality = False;
|
||||
param_equality = False
|
||||
|
||||
|
||||
if isinstance(params[0], expected) or param_equality:
|
||||
|
@ -982,7 +1015,7 @@ class MockMethod(object):
|
|||
"""
|
||||
|
||||
def __init__(self, method_name, call_queue, replay_mode,
|
||||
method_to_mock=None, description=None):
|
||||
method_to_mock=None, description=None, class_to_bind=None):
|
||||
"""Construct a new mock method.
|
||||
|
||||
Args:
|
||||
|
@ -994,11 +1027,16 @@ class MockMethod(object):
|
|||
# method_to_mock: The actual method being mocked, used for introspection.
|
||||
# description: optionally, a descriptive name for this method. Typically
|
||||
# this is equal to the descriptive name of the method's class.
|
||||
# class_to_bind: optionally, a class that is used for unbound methods
|
||||
# (or functions in Python3) to which method is bound, in order not
|
||||
# to loose binding information. If given, it will be used for
|
||||
# checking the type of first method parameter
|
||||
method_name: str
|
||||
call_queue: list or deque
|
||||
replay_mode: bool
|
||||
method_to_mock: a method object
|
||||
description: str or None
|
||||
class_to_bind: type or None
|
||||
"""
|
||||
|
||||
self._name = method_name
|
||||
|
@ -1016,7 +1054,8 @@ class MockMethod(object):
|
|||
self._side_effects = None
|
||||
|
||||
try:
|
||||
self._checker = MethodSignatureChecker(method_to_mock)
|
||||
self._checker = MethodSignatureChecker(method_to_mock,
|
||||
class_to_bind=class_to_bind)
|
||||
except ValueError:
|
||||
self._checker = None
|
||||
|
||||
|
@ -1069,7 +1108,7 @@ class MockMethod(object):
|
|||
"""Raise a TypeError with a helpful message."""
|
||||
raise TypeError('MockMethod cannot be iterated. '
|
||||
'Did you remember to put your mocks in replay mode?')
|
||||
|
||||
|
||||
def __next__(self):
|
||||
"""Raise a TypeError with a helpful message."""
|
||||
raise TypeError('MockMethod cannot be iterated. '
|
||||
|
@ -1117,7 +1156,7 @@ class MockMethod(object):
|
|||
if self._description:
|
||||
full_desc = "%s.%s" % (self._description, full_desc)
|
||||
return full_desc
|
||||
|
||||
|
||||
def __hash__(self):
|
||||
return id(self)
|
||||
|
||||
|
@ -1643,7 +1682,7 @@ class SameElementsAs(Comparator):
|
|||
# This happens because Mox uses __eq__ both to check object equality (in
|
||||
# MethodSignatureChecker) and to invoke Comparators.
|
||||
return False
|
||||
|
||||
|
||||
try:
|
||||
return set(self._expected_list) == set(actual_list)
|
||||
except TypeError:
|
||||
|
|
24
mox_test.py
24
mox_test.py
|
@ -749,7 +749,8 @@ class MethodCheckerTest(unittest.TestCase):
|
|||
|
||||
def testNoParameters(self):
|
||||
method = mox.MockMethod('NoParameters', [], False,
|
||||
CheckCallTestClass.NoParameters)
|
||||
CheckCallTestClass.NoParameters,
|
||||
class_to_bind=CheckCallTestClass)
|
||||
method()
|
||||
self.assertRaises(AttributeError, method, 1)
|
||||
self.assertRaises(AttributeError, method, 1, 2)
|
||||
|
@ -758,7 +759,8 @@ class MethodCheckerTest(unittest.TestCase):
|
|||
|
||||
def testOneParameter(self):
|
||||
method = mox.MockMethod('OneParameter', [], False,
|
||||
CheckCallTestClass.OneParameter)
|
||||
CheckCallTestClass.OneParameter,
|
||||
class_to_bind=CheckCallTestClass)
|
||||
self.assertRaises(AttributeError, method)
|
||||
method(1)
|
||||
method(a=1)
|
||||
|
@ -769,7 +771,8 @@ class MethodCheckerTest(unittest.TestCase):
|
|||
|
||||
def testTwoParameters(self):
|
||||
method = mox.MockMethod('TwoParameters', [], False,
|
||||
CheckCallTestClass.TwoParameters)
|
||||
CheckCallTestClass.TwoParameters,
|
||||
class_to_bind=CheckCallTestClass)
|
||||
self.assertRaises(AttributeError, method)
|
||||
self.assertRaises(AttributeError, method, 1)
|
||||
self.assertRaises(AttributeError, method, a=1)
|
||||
|
@ -786,7 +789,8 @@ class MethodCheckerTest(unittest.TestCase):
|
|||
|
||||
def testOneDefaultValue(self):
|
||||
method = mox.MockMethod('OneDefaultValue', [], False,
|
||||
CheckCallTestClass.OneDefaultValue)
|
||||
CheckCallTestClass.OneDefaultValue,
|
||||
class_to_bind=CheckCallTestClass)
|
||||
method()
|
||||
method(1)
|
||||
method(a=1)
|
||||
|
@ -797,7 +801,8 @@ class MethodCheckerTest(unittest.TestCase):
|
|||
|
||||
def testTwoDefaultValues(self):
|
||||
method = mox.MockMethod('TwoDefaultValues', [], False,
|
||||
CheckCallTestClass.TwoDefaultValues)
|
||||
CheckCallTestClass.TwoDefaultValues,
|
||||
class_to_bind=CheckCallTestClass)
|
||||
self.assertRaises(AttributeError, method)
|
||||
self.assertRaises(AttributeError, method, c=3)
|
||||
self.assertRaises(AttributeError, method, 1)
|
||||
|
@ -816,7 +821,8 @@ class MethodCheckerTest(unittest.TestCase):
|
|||
self.assertRaises(AttributeError, method, a=1, b=2, e=9)
|
||||
|
||||
def testArgs(self):
|
||||
method = mox.MockMethod('Args', [], False, CheckCallTestClass.Args)
|
||||
method = mox.MockMethod('Args', [], False, CheckCallTestClass.Args,
|
||||
class_to_bind=CheckCallTestClass)
|
||||
self.assertRaises(AttributeError, method)
|
||||
self.assertRaises(AttributeError, method, 1)
|
||||
method(1, 2)
|
||||
|
@ -827,7 +833,8 @@ class MethodCheckerTest(unittest.TestCase):
|
|||
self.assertRaises(AttributeError, method, 1, 2, c=3)
|
||||
|
||||
def testKwargs(self):
|
||||
method = mox.MockMethod('Kwargs', [], False, CheckCallTestClass.Kwargs)
|
||||
method = mox.MockMethod('Kwargs', [], False, CheckCallTestClass.Kwargs,
|
||||
class_to_bind=CheckCallTestClass)
|
||||
self.assertRaises(AttributeError, method)
|
||||
method(1)
|
||||
method(1, 2)
|
||||
|
@ -843,7 +850,8 @@ class MethodCheckerTest(unittest.TestCase):
|
|||
|
||||
def testArgsAndKwargs(self):
|
||||
method = mox.MockMethod('ArgsAndKwargs', [], False,
|
||||
CheckCallTestClass.ArgsAndKwargs)
|
||||
CheckCallTestClass.ArgsAndKwargs,
|
||||
class_to_bind=CheckCallTestClass)
|
||||
self.assertRaises(AttributeError, method)
|
||||
method(1)
|
||||
method(1, 2)
|
||||
|
|
Loading…
Reference in New Issue