Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for signature copying #8

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ start with ``mock_``, while the constructor arguments don't.
object the tracker will be notified. Otherwise attribute sets are
silent, and only calls trigger notification.

``copy_signature_of``:
If given, the function or object signature will be copied to the
mock object. This can be useful when testing codes that rely
on metaprogramming.

So to create an object that always raises ValueError, do::

>>> dummy_module = Mock('mylibrary')
Expand Down
42 changes: 38 additions & 4 deletions minimock.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
got. We've provided fake return calls (for the ``smtplib.SMTP()``
constructor). These are all the core parts of a mock library. The
implementation is simple because most of the work is done by doctest.

>>> restore()

"""

__all__ = ("mock", "restore", "Mock", "TraceTracker", "assert_same_trace")
Expand Down Expand Up @@ -111,7 +114,7 @@ def lookup_by_name(name, nsdicts):
raise NameError("name '%s' is not defined" % name)


def mock(name, nsdicts=None, mock_obj=None, **kw):
def mock(name, nsdicts=None, mock_obj=None, copy_signature=False, **kw):
"""
Mock the named object, placing a Mock instance in the correct namespace
dictionary. If no iterable of namespace dicts is provided, use
Expand Down Expand Up @@ -211,11 +214,11 @@ def mock(name, nsdicts=None, mock_obj=None, **kw):
finally:
del(stack)

nsdict, obj_name, attrs = lookup_by_name(name, nsdicts)

if mock_obj is None:
mock_obj = Mock(name, **kw)

nsdict, obj_name, attrs = lookup_by_name(name, nsdicts)

# Get the original object and replace it with the mock object.
tmp = nsdict[obj_name]

Expand All @@ -241,6 +244,9 @@ def mock(name, nsdicts=None, mock_obj=None, **kw):
original = getattr(tmp, attrs[-1])
setattr(tmp, attrs[-1], mock_obj)

if copy_signature:
mock_obj.mock_signature = original

mocked.append((original, nsdict, obj_name, attrs))


Expand Down Expand Up @@ -506,7 +512,7 @@ class Mock(object):

def __init__(self, name, returns=None, returns_iter=None,
returns_func=None, raises=None, show_attrs=False,
tracker=DefaultTracker, **kw):
tracker=DefaultTracker, copy_signature_of=None, **kw):
_obsetattr = object.__setattr__
_obsetattr(self, 'mock_name', name)
_obsetattr(self, 'mock_returns', returns)
Expand All @@ -520,6 +526,9 @@ def __init__(self, name, returns=None, returns_iter=None,
if tracker is DefaultTracker:
tracker = Printer(sys.stdout)
_obsetattr(self, 'mock_tracker', tracker)
if copy_signature_of is not None:
signature = inspect.signature(copy_signature_of)
_obsetattr(self, '__signature__', signature)

def __repr__(self):
return '<Mock %s %s>' % (hex(id(self)), self.mock_name)
Expand Down Expand Up @@ -563,9 +572,13 @@ def __setattr__(self, attr, value):
'mock_returns_iter',
'mock_tracker',
'mock_show_attrs',
'mock_signature'
)):
if attr == 'mock_returns_iter' and value is not None:
value = iter(value)
elif attr == 'mock_signature':
attr = '__signature__'
value = inspect.signature(value)
object.__setattr__(self, attr, value)
else:
if self.mock_show_attrs and self.mock_tracker is not None:
Expand Down Expand Up @@ -645,6 +658,27 @@ def __setattr__(self, attr, value):
Called smtp_connection.quit()
>>> restore()

Mimics signature of a function/class.

>>> mock("send_email", copy_signature=True, returns=3)
>>> send_email # doctest: +ELLIPSIS
<Mock 0x... send_email>
>>> inspect.signature(send_email)
<Signature (from_addr, to_addr, subject, body)>
>>> restore()

Works with objects defined outside module.

>>> import os
>>> inspect.signature(os.path.join) # doctest: +ELLIPSIS
<Signature (path, *paths)>
>>> mock("os.path.join", copy_signature=True)
>>> os.path.join # doctest: +ELLIPSIS
<Mock 0x... os.path.join>
>>> inspect.signature(os.path.join)
<Signature (path, *paths)>
>>> restore()

""",
}

Expand Down