Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions doubles/proxy_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ def restore_original_method(self):
setattr(self._target.obj, self._method_name, self._original_method)
elif self._attr.kind == 'property':
setattr(self._target.obj.__class__, self._method_name, self._original_method)
del self._target.obj.__dict__[double_name(self._method_name)]
delattr(self._target.obj, double_name(self._method_name))
elif self._attr.kind == 'attribute':
self._target.obj.__dict__[self._method_name] = self._original_method
setattr(self._target.obj, self._method_name, self._original_method)
else:
# TODO: Could there ever have been a value here that needs to be restored?
del self._target.obj.__dict__[self._method_name]
delattr(self._target.obj, self._method_name)

if self._method_name in ['__call__', '__enter__', '__exit__']:
self._target.restore_attr(self._method_name)
Expand All @@ -135,9 +135,9 @@ def _hijack_target(self):
self._original_method,
)
setattr(self._target.obj.__class__, self._method_name, proxy_property)
self._target.obj.__dict__[double_name(self._method_name)] = self
setattr(self._target.obj, double_name(self._method_name), self)
else:
self._target.obj.__dict__[self._method_name] = self
setattr(self._target.obj, self._method_name, self)

if self._method_name in ['__call__', '__enter__', '__exit__']:
self._target.hijack_attr(self._method_name)
Expand Down
4 changes: 2 additions & 2 deletions doubles/proxy_property.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ def __init__(self, name, original):
self._original = original

def __get__(self, obj, objtype=None):
if self._name in obj.__dict__:
return obj.__dict__[self._name].__get__(obj, objtype)
if hasattr(obj, self._name):
return getattr(obj, self._name).__get__(obj, objtype)
return self._original.__get__(obj, objtype)
12 changes: 5 additions & 7 deletions doubles/targets/allowance_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,8 @@ def __getattribute__(self, attr_name):
:rtype: object, Allowance
"""

__dict__ = object.__getattribute__(self, '__dict__')

if __dict__ and attr_name in __dict__:
return __dict__[attr_name]

caller = inspect.getframeinfo(inspect.currentframe().f_back)
return self._proxy.add_allowance(attr_name, caller)
try:
return object.__getattribute__(self, attr_name)
except AttributeError:
caller = inspect.getframeinfo(inspect.currentframe().f_back)
return self._proxy.add_allowance(attr_name, caller)
62 changes: 62 additions & 0 deletions doubles/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,68 @@ class User(OldStyleUser, object):
pass


class UserWithSlots(object):
__slots__ = (
'name',
'age',
'callable_instance_attribute'
)

"""An importable dummy class used for testing purposes."""

class_attribute = 'foo'
callable_class_attribute = classmethod(lambda cls: 'dummy result')
arbitrary_callable = ArbitraryCallable('ArbitraryCallable Value')

def __init__(self, name, age):
self.name = name
self.age = age
self.callable_instance_attribute = lambda: 'dummy result'

@staticmethod
def static_method(arg):
return 'static_method return value: {}'.format(arg)

@classmethod
def class_method(cls, arg):
return 'class_method return value: {}'.format(arg)

def get_name(self):
return self.name

def instance_method(self):
return 'instance_method return value'

def method_with_varargs(self, *args):
return 'method_with_varargs return value'

def method_with_default_args(self, foo, bar='baz'):
return 'method_with_default_args return value'

def method_with_varkwargs(self, **kwargs):
return 'method_with_varkwargs return value'

def method_with_positional_arguments(self, foo):
return 'method_with_positional_arguments return value'

def method_with_doc(self):
"""A basic method of UserWithSlots to illustrate existance of a docstring"""
return

@property
def some_property(self):
return 'some_property return value'

def __call__(self, *args):
return 'user was called'

def __enter__(self):
return self

def __exit__(self, exc_type, exc_value, traceback):
pass


class UserWithCustomNew(User):
def __new__(cls, name, age):
instance = User.__new__(cls)
Expand Down
5 changes: 4 additions & 1 deletion test/class_double_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,23 @@
from doubles.testing import (
User,
OldStyleUser,
UserWithSlots,
EmptyClass,
OldStyleEmptyClass,
)

TEST_CLASSES = (
'doubles.testing.User',
'doubles.testing.OldStyleUser',
'doubles.testing.UserWithSlots',
'doubles.testing.EmptyClass',
'doubles.testing.OldStyleEmptyClass',
)

VALID_ARGS = {
'doubles.testing.User': ('Bob', 100),
'doubles.testing.OldStyleUser': ('Bob', 100),
'doubles.testing.UserWithSlots': ('Bob', 100),
'doubles.testing.EmptyClass': tuple(),
'doubles.testing.OldStyleEmptyClass': tuple(),
}
Expand Down Expand Up @@ -166,7 +169,7 @@ def test_with_valid_args(self, test_class):
assert TestClass(*VALID_ARGS[test_class]) is None


@mark.parametrize('test_class', [User, OldStyleUser, EmptyClass, OldStyleEmptyClass])
@mark.parametrize('test_class', [User, OldStyleUser, UserWithSlots, EmptyClass, OldStyleEmptyClass])
class TestingStubbingNonClassDoubleConstructors(object):
def test_raises_if_you_allow_constructor(self, test_class):
with raises(ConstructorDoubleError):
Expand Down
11 changes: 6 additions & 5 deletions test/object_double_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,27 @@
from doubles.exceptions import VerifyingDoubleArgumentError, VerifyingDoubleError
from doubles.object_double import ObjectDouble
from doubles.targets.allowance_target import allow
from doubles.testing import User, OldStyleUser
from doubles.testing import User, OldStyleUser, UserWithSlots

user = User('Alice', 25)
old_style_user = OldStyleUser('Alice', 25)
user_with_slots = UserWithSlots('Alice', 25)


@mark.parametrize('test_object', [user, old_style_user])
@mark.parametrize('test_object', [user, old_style_user, user_with_slots])
class TestRepr(object):
def test_displays_correct_class_name(self, test_object):
subject = ObjectDouble(test_object)

assert re.match(
r"<ObjectDouble of <doubles.testing.(?:OldStyle)?User "
r"<ObjectDouble of <doubles.testing.%s "
r"(?:instance|object) at 0x[0-9a-f]+> object "
r"at 0x[0-9a-f]+>",
r"at 0x[0-9a-f]+>" % test_object.__class__.__name__,
repr(subject)
)


@mark.parametrize('test_object', [user, old_style_user])
@mark.parametrize('test_object', [user, old_style_user, user_with_slots])
class TestObjectDouble(object):
def test_allows_stubs_on_existing_methods(self, test_object):
doubled_user = ObjectDouble(test_object)
Expand Down
12 changes: 6 additions & 6 deletions test/partial_double_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
)
from doubles.lifecycle import teardown
from doubles import allow, no_builtin_verification
from doubles.testing import User, OldStyleUser, UserWithCustomNew
from doubles.testing import User, OldStyleUser, UserWithSlots, UserWithCustomNew
import doubles.testing


@mark.parametrize('test_class', [User, OldStyleUser])
@mark.parametrize('test_class', [User, OldStyleUser, UserWithSlots])
class TestInstanceMethods(object):
def test_arbitrary_callable_on_instance(self, test_class):
instance = test_class('Bob', 10)
Expand Down Expand Up @@ -121,7 +121,7 @@ def test_teardown_restores_properties(self, test_class):
assert user_2.some_property == 'some_property return value'


@mark.parametrize('test_class', [User, OldStyleUser])
@mark.parametrize('test_class', [User, OldStyleUser, UserWithSlots])
class Test__call__(object):
def test_basic_usage(self, test_class):
user = test_class('Alice', 25)
Expand Down Expand Up @@ -177,7 +177,7 @@ def test_raises_when_mocked_with_invalid_call_signature(self, test_class):
allow(user).__call__.with_args(1, 2, bob='barker')


@mark.parametrize('test_class', [User, OldStyleUser])
@mark.parametrize('test_class', [User, OldStyleUser, UserWithSlots])
class Test__enter__(object):
def test_basic_usage(self, test_class):
user = test_class('Alice', 25)
Expand Down Expand Up @@ -229,7 +229,7 @@ def test_raises_when_mocked_with_invalid_call_signature(self, test_class):
allow(user).__enter__.with_args(1)


@mark.parametrize('test_class', [User, OldStyleUser])
@mark.parametrize('test_class', [User, OldStyleUser, UserWithSlots])
class Test__exit__(object):
def test_basic_usage(self, test_class):
user = test_class('Alice', 25)
Expand Down Expand Up @@ -273,7 +273,7 @@ def test_raises_when_mocked_with_invalid_call_signature(self, test_class):
allow(user).__exit__.with_no_args()


@mark.parametrize('test_class', [User, OldStyleUser])
@mark.parametrize('test_class', [User, OldStyleUser, UserWithSlots])
class TestClassMethods(object):
def test_stubs_class_methods(self, test_class):
allow(test_class).class_method.with_args('foo').and_return('overridden value')
Expand Down