summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGustavo Niemeyer <gustavo@niemeyer.net>2007-10-29 00:13:18 -0200
committerGustavo Niemeyer <gustavo@niemeyer.net>2007-10-29 00:13:18 -0200
commitdddccbae4b89957a5664462b1ca953357d51d59e (patch)
treeff53d141c9f2af07e535bbfd08f5b97bf30c6086
parent672b6a5b727c10a32c5274aee4e11a639f5ae27f (diff)
downloadmocker-dddccbae4b89957a5664462b1ca953357d51d59e.tar.gz
Moved it forward, most importantly implementing the patching feature.
-rw-r--r--mocker.py885
-rwxr-xr-xtest.py1333
2 files changed, 1744 insertions, 474 deletions
diff --git a/mocker.py b/mocker.py
index 1946dd9..6fffcdd 100644
--- a/mocker.py
+++ b/mocker.py
@@ -1,15 +1,21 @@
+import __builtin__
import inspect
+import types
import sys
+import os
import gc
__all__ = ["Mocker", "expect", "SAME", "CONTAINS", "ANY", "VARIOUS"]
+ERROR_PREFIX = "[Mocker] "
+
+
# --------------------------------------------------------------------
# Exceptions
-class UnexpectedExprError(AssertionError):
+class MatchError(AssertionError):
"""Raised when an unknown expression is seen in playback mode."""
@@ -73,7 +79,55 @@ RESTORE = State("RESTORE")
class MockerBase(object):
- """This is the implementation of Mocker, without any recorders."""
+ """Controller of mock objects.
+
+ A mocker instance is used to command recording and replay of
+ expectations on any number of mock objects.
+
+ Expectations should be expressed for the mock object while in
+ record mode (the initial one) by using the mock object itself,
+ and using the mocker (and/or C{expect()} as a helper) to define
+ additional behavior for each event. For instance:
+
+ mock = mocker.mock()
+ mock.hello()
+ mocker.result(10)
+ mocker.replay()
+ assert mock.hello() == 10
+ mock.restore()
+ mock.verify()
+
+ In this short excerpt a mock object is being created, then an
+ expectation of a call to the C{hello()} method was recorded, and
+ when that happens the method should return the value C{10}. Then,
+ the mocker is put in replay mode, and the expectation is satisfied
+ by calling the C{hello()} method, which indeed returns 10. Finally,
+ a call to the L{restore()} method is performed to undo any needed
+ changes made in the environment, and the L{verify()} method is
+ called to ensure that all defined expectations were met.
+
+ The same logic can be expressed more elegantly using the
+ C{with mocker:} statement, as follows::
+
+ mock = mocker.mock()
+ mock.hello()
+ mocker.result(10)
+ with mocker:
+ assert mock.hello() == 10
+
+ Also, the MockerTestCase class, which integrates the mocker on
+ a unittest.TestCase subclass, may be used to reduce the overhead
+ of controlling the mocker. A test could be written as follows::
+
+ class SampleTest(MockerTestCase):
+
+ def test_hello(self):
+ mock = self.mocker.mock()
+ mock.hello()
+ self.mocker.result(10)
+ self.mocker.replay()
+ assert mock.hello() == 10
+ """
_recorders = []
@@ -92,39 +146,105 @@ class MockerBase(object):
self._ordering = False
self._last_orderer = None
- def get_state(self):
- return self._state
-
def _set_state(self, state):
if state is not self._state:
self._state = state
for event in self._events:
event.set_state(state)
+ def get_state(self):
+ """Return the current state (RECORD, REPLAY or RESTORE).
+
+ The initial state is RECORD.
+ """
+ return self._state
+
def replay(self):
+ """Change state to expect recorded events to be reproduced.
+
+ An alternative and more comfortable way to replay changes is
+ using the 'with' statement, as follows::
+
+ mocker = Mocker()
+ <record events>
+ with mocker:
+ <reproduce events>
+
+ The 'with' statement will automatically put mocker in replay
+ mode, and will also verify if all events were correctly reproduced
+ at the end (using L{verify()}), and also restore any changes done
+ in the environment (with L{restore()}).
+
+ Also check the MockerTestCase class, which integrates the
+ unittest.TestCase class with mocker.
+ """
self._set_state(REPLAY)
def record(self):
+ """Put the mocker on recording mode, where expectations are defined.
+
+ That's the initial state when the mocker is constructed.
+ """
self._set_state(RECORD)
def restore(self):
+ """Restore all changes done in the environment.
+
+ This should always be called after the test is complete (succeeding
+ or not). There are ways to call this method automatically on
+ completion (e.g. using a with mocker: statement, or using the
+ MockerTestCase class.
+ """
self._set_state(RESTORE)
def is_ordering(self):
+ """Return true if all events are being ordered.
+
+ See the L{ordered()} method.
+ """
return self._ordering
def ordered(self):
+ """Expect following events to be reproduced in the recorded order.
+
+ By default, mocker won't force events to happen precisely in
+ the order they were recorded. Calling this method will change
+ this behavior so that events will only match if reproduced in
+ the correct order.
+
+ Running the L{unordered()} method will put the mocker back on
+ unordered mode.
+
+ This method may also be used with the 'with' statement, like so::
+
+ with mocker.ordered():
+ <record events>
+
+ In this case, only expressions in <record events> will be ordered,
+ and the mocker will be back in unordered mode after the 'with' block.
+ """
self._ordering = True
return OrderedContext(self)
def unordered(self):
+ """Don't expect following events to be reproduce in the recorded order.
+
+ This will undo the effect of the L{ordered()} method, putting the
+ mocker back in the default unordered mode.
+ """
self._ordering = False
self._last_orderer = None
def get_events(self):
+ """Return all recorded events."""
return self._events[:]
def add_event(self, event):
+ """Add an event.
+
+ This method is used internally by the implementation, and
+ shouldn't be needed on normal mocker usage.
+ """
self._events.append(event)
if self._ordering:
orderer = event.add_task(Orderer())
@@ -134,34 +254,178 @@ class MockerBase(object):
return event
def verify(self):
- for event in self._events:
- event.verify()
+ """Check if all expectations were met, and raise AssertionError if not.
- def obj(self, spec=None):
- """Return a new mock object."""
- return Mock(self, spec=spec)
+ The exception message will include a nice description of which
+ expectations were not met, and why.
+ """
+ errors = []
+ for event in self._events:
+ try:
+ event.verify()
+ except AssertionError, e:
+ error = str(e)
+ if not error:
+ raise RuntimeError("Empty error message from %r"
+ % event)
+ errors.append(error)
+ if errors:
+ message = [ERROR_PREFIX + "Unmet expectations:", ""]
+ for error in errors:
+ lines = error.splitlines()
+ message.append("=> " + lines.pop(0))
+ message.extend(" " + line for line in lines)
+ message.append("")
+ raise AssertionError(os.linesep.join(message))
+
+ def mock(self, spec_and_type=None, spec=None, type=None, name=None):
+ """Return a new mock object.
+
+ @param spec_and_type: Handy positional argument which sets both
+ spec and type.
+ @param spec: Method calls will be checked for correctness against
+ the given class.
+ @param type: If set, the Mock's __class__ attribute will return
+ the given type. This will make C{isinstance()} calls
+ on the object work.
+ @param name: Name for the mock object, used in the representation of
+ expressions. The name is rarely needed, as it's usually
+ guessed correctly from the variable name used.
+ """
+ if spec_and_type is not None:
+ spec = type = spec_and_type
+ return Mock(self, spec=spec, type=type, name=name)
- def proxy(self, object, spec=None, name=None,
- passthrough=True, install=False):
+ def proxy(self, object, spec=True, type=True, name=None, passthrough=True):
"""Return a new mock object which proxies to the given object.
- Unknown expressions are passed through to the proxied object
- by default, unless passthrough is set to False. When set to
- False, pass through only happens when explicitly requested.
+ Proxies are useful when only part of the behavior of an object
+ is to be mocked. Unknown expressions may be passed through to
+ the real implementation implicitly (if the C{passthrough} argument
+ is True), or explicitly (using the L{passthrough()} method
+ on the event).
+
+ @param object: Real object to be proxied.
+ @param spec: Method calls will be checked for correctness against
+ the given object, which may be a class or an instance
+ where attributes will be looked up. Defaults to the
+ the C{object} parameter. May be set to None explicitly,
+ in which case spec checking is disabled. Checks may
+ also be disabled explicitly on a per-event basis with
+ the L{nospec()} method.
+ @param type: If set, the Mock's __class__ attribute will return
+ the given type. This will make C{isinstance()} calls
+ on the object work. Defaults to the type of the
+ C{object} parameter. May be set to None explicitly.
+ @param name: Name for the mock object, used in the representation of
+ expressions. The name is rarely needed, as it's usually
+ guessed correctly from the variable name used.
+ @param passthrough: If set to False, passthrough of actions on the
+ proxy to the real object will only happen when
+ explicitly requested via the L{passthrough()}
+ method.
"""
- mock = Mock(self, spec=spec, object=object,
+ if spec is True:
+ spec = object
+ if type is True:
+ type = __builtin__.type(object)
+ return Mock(self, spec=spec, type=type, object=object,
name=name, passthrough=passthrough)
- if install:
- event = self.add_event(Event())
- event.add_task(ProxyInstaller(mock))
+
+ def replace(self, object, spec=True, type=True, name=None,
+ passthrough=True):
+ """Create a proxy, and replace the original object with the mock.
+
+ On replay, the original object will be replaced by the returned
+ proxy in all dictionaries found in the running interpreter via
+ the garbage collecting system. This should cover module
+ namespaces, class namespaces, instance namespaces, and so on.
+
+ @param object: Real object to be proxied, and replaced by the mock
+ on replay mode.
+ @param spec: Method calls will be checked for correctness against
+ the given object, which may be a class or an instance
+ where attributes will be looked up. Defaults to the
+ the C{object} parameter. May be set to None explicitly,
+ in which case spec checking is disabled. Checks may
+ also be disabled explicitly on a per-event basis with
+ the L{nospec()} method.
+ @param type: If set, the Mock's __class__ attribute will return
+ the given type. This will make C{isinstance()} calls
+ on the object work. Defaults to the type of the
+ C{object} parameter. May be set to None explicitly.
+ @param name: Name for the mock object, used in the representation of
+ expressions. The name is rarely needed, as it's usually
+ guessed correctly from the variable name used.
+ @param passthrough: If set to False, passthrough of actions on the
+ proxy to the real object will only happen when
+ explicitly requested via the L{passthrough()}
+ method.
+ """
+ if isinstance(object, basestring):
+ if name is None:
+ name = object
+ import_stack = object.split(".")
+ attr_stack = []
+ while import_stack:
+ module_path = ".".join(import_stack)
+ try:
+ object = __import__(module_path, {}, {}, [""])
+ except ImportError:
+ attr_stack.insert(0, import_stack.pop())
+ continue
+ else:
+ for attr in attr_stack:
+ object = getattr(object, attr)
+ break
+ mock = self.proxy(object, spec, type, name, passthrough)
+ event = self.add_event(Event())
+ event.add_task(ProxyReplacer(mock))
return mock
- def module(self, name, passthrough=True):
- return self.proxy(__import__(name, {}, {}, [""]),
- name=name, passthrough=passthrough, install=True)
+ def patch(self, object):
+ """Patch an existing object to reproduce recorded events.
+
+ @param object: Class or instance to be patched.
+
+ The result of this method is still a mock object, which can be
+ used like any other mock object to record events. The difference
+ is that when the mocker is put on replay mode, the *real* object
+ will be modified to behave according to recorded expectations.
+
+ Patching works in individual instances, and also in classes.
+ When an instance is patched, recorded events will only be
+ considered on this specific instance, and other instances should
+ behave normally. When a class is patched, the reproduction of
+ events will be considered on any instance of this class once
+ created (collectively).
+
+ Observe that, unlike with proxies which catch only events done
+ through the mock object, *all* accesses to recorded expectations
+ will be considered; even these coming from the object itself
+ (e.g. C{self.hello()} is considered if this method was patched).
+ While this is a very powerful feature, and many times the reason
+ to use patches in the first place, it's important to keep this
+ behavior in mind.
+
+ Patching of the original object only takes place when the mocker
+ is put on replay mode, and the patched object will be restored
+ to its original state once the L{restore()} method is called
+ (explicitly, or implicitly with alternative conventions, such as
+ a C{with mocker:} block, or a MockerTestCase class).
+ """
+ patcher = Patcher()
+ event = self.add_event(Event())
+ event.add_task(patcher)
+ mock = Mock(self, object=object, patcher=patcher, passthrough=True)
+ object.__mocker_mock__ = mock
+ return mock
def act(self, path):
"""This is called by mock objects whenever something happens to them.
+
+ This method is part of the implementation between the mocker
+ and mock objects.
"""
if self._state is RECORD:
event = self.add_event(Event(path))
@@ -172,48 +436,83 @@ class MockerBase(object):
for event in sorted(self._events, key=lambda e:e.satisfied()):
if event.matches(path):
return event.run(path)
- raise UnexpectedExprError("[Mocker] Unexpected expression: %s"
- % path)
+ raise MatchError(ERROR_PREFIX + "Unexpected expression: %s" % path)
# XXX Show the full state here.
else:
- raise RuntimeError("Mocker isn't recording or replaying.")
+ raise RuntimeError(ERROR_PREFIX + "Not in record or playback mode")
@classinstancemethod
def get_recorders(cls, self):
+ """Return recorders associated with this mocker class or instance.
+
+ This method may be called on mocker instances and also on mocker
+ classes. See the L{add_recorder()} method for more information.
+ """
return (self or cls)._recorders[:]
@classinstancemethod
def add_recorder(cls, self, recorder):
+ """Add a recorder to this mocker class or instance.
+
+ @param recorder: Callable accepting C{(mocker, event)} as parameters.
+
+ This is part of the implementation of mocker.
+
+ All registered recorders are called for translating events that
+ happen during recording into expectations to be met once the state
+ is switched to replay mode.
+
+ This method may be called on mocker instances and also on mocker
+ classes. When called on a class, the recorder will be used by
+ all instances, and also inherited on subclassing. When called on
+ instances, the recorder is added only to the given instance.
+ """
(self or cls)._recorders.append(recorder)
return recorder
@classinstancemethod
def remove_recorder(cls, self, recorder):
+ """Remove the given recorder from this mocker class or instance.
+
+ This method may be called on mocker classes and also on mocker
+ instances. See the L{add_recorder()} method for more information.
+ """
(self or cls)._recorders.remove(recorder)
def result(self, value):
- """Last recorded event will return the given value."""
+ """Make the last recorded event return the given value on replay.
+
+ @param value: Object to be returned when the event is replayed.
+ """
self.call(lambda *args, **kwargs: value)
def throw(self, exception):
- """Last recorded event will raise the given exception."""
+ """Make the last recorded event raise the given exception on replay.
+
+ @param exception: Class or instance of exception to be raised.
+ """
def raise_exception(*args, **kwargs):
raise exception
self.call(raise_exception)
def call(self, func):
- """Last recorded event will cause the given function to be called.
+ """Make the last recorded event cause the given function to be called.
+
+ @param func: Function to be called.
The result of the function will be used as the event result.
"""
self._events[-1].add_task(FunctionRunner(func))
def count(self, min, max=False):
- """Last recorded event must happen between min and max times.
-
- If the max argument isn't given, it's assumed to be the same
- as min. If max is None, the event must just happen at least
- min times.
+ """Last recorded event must be replayed between min and max times.
+
+ @param min: Minimum number of times that the event must happen.
+ @param max: Maximum number of times that the event must happen. If
+ not given, it defaults to the same value of the C{min}
+ parameter. If set to None, there is no upper limit, and
+ the expectation is met as long as it happens at least
+ C{min} times.
"""
event = self._events[-1]
for task in event.get_tasks():
@@ -224,8 +523,20 @@ class MockerBase(object):
def order(self, *path_holders):
"""Ensure that events referred to by given objects happen in order.
- Arguments passed to this method might be mock objects returned
- from recorded operations.
+ @param *path_holders: Objects returned as the result of recorded
+ events.
+
+ As an example::
+
+ mock = mocker.mock()
+ expr1 = mock.hello()
+ expr2 = mock.world
+ expr3 = mock.x.y.z
+ mocker.order(expr1, expr2, expr3)
+
+ This method of ordering only works when the expression returns
+ another object. For other methods of ordering check the
+ L{ordered()}, L{after()}, and L{before()} methods.
"""
last_orderer = None
for path_holder in path_holders:
@@ -250,8 +561,10 @@ class MockerBase(object):
def after(self, *path_holders):
"""Last recorded event must happen after events referred to.
- Arguments passed to this method might be mock objects returned
- from recorded operations.
+ @param *path_holders: Objects returned as the result of recorded
+ events.
+
+ See L{order()} for more information.
"""
last_path = self._events[-1].path
for path_holder in path_holders:
@@ -260,25 +573,49 @@ class MockerBase(object):
def before(self, *path_holders):
"""Last recorded event must happen before events referred to.
- Arguments passed to this method might be mock objects returned
- from recorded operations.
+ @param *path_holders: Objects returned as the result of recorded
+ events.
+
+ See L{order()} for more information.
"""
last_path = self._events[-1].path
for path_holder in path_holders:
self.order(last_path, path_holder)
def nospec(self):
- """Don't check method specification of real class on last event."""
+ """Don't check method specification of real object on last event."""
event = self._events[-1]
for task in event.get_tasks():
if isinstance(task, SpecChecker):
event.remove_task(task)
def passthrough(self):
+ """Make the last recorder event act on the real object once seen.
+
+ This can only be used on proxies, as returned by the L{proxy()}
+ and L{replace()} methods, or on patched objects (L{patch()}).
+ """
event = self._events[-1]
- if not event.path.root_mock.__mocker_object__:
+ if event.path.root_object is None:
raise TypeError("Mock object isn't a proxy")
- event.add_task(PathApplier())
+ event.add_task(PathExecuter())
+
+ def __enter__(self):
+ """Enter in a 'with' context. This will run replay()."""
+ self.replay()
+ return self
+
+ def __exit__(self, type, value, traceback):
+ """Exit from a 'with' context.
+
+ This will run restore() at all times, but will only run verify()
+ if the 'with' block itself hasn't raised an exception. Exceptions
+ in that block are never swallowed.
+ """
+ self.restore()
+ if type is None:
+ self.verify()
+ return False
class OrderedContext(object):
@@ -294,7 +631,7 @@ class OrderedContext(object):
class Mocker(MockerBase):
- pass
+ __doc__ = MockerBase.__doc__
# Decorator to add recorders on the standard Mocker class.
recorder = Mocker.add_recorder
@@ -305,36 +642,53 @@ recorder = Mocker.add_recorder
class Mock(object):
- def __init__(self, mocker, path=None, name=None, spec=None,
- object=None, passthrough=False):
+ def __init__(self, mocker, path=None, name=None, spec=None, type=None,
+ object=None, passthrough=False, patcher=None):
self.__mocker__ = mocker
- self.__mocker_path__ = path or Path(self)
+ self.__mocker_path__ = path or Path(self, object)
self.__mocker_name__ = name
self.__mocker_spec__ = spec
self.__mocker_object__ = object
self.__mocker_passthrough__ = passthrough
+ self.__mocker_patcher__ = patcher
+ self.__mocker_replace__ = False
+ self.__mocker_type__ = type
- def __mocker_act__(self, kind, *args, **kwargs):
+ def __mocker_act__(self, kind, args=(), kwargs={}, object=None):
if self.__mocker_name__ is None:
self.__mocker_name__ = find_object_name(self, 2)
- action = Action(self.__mocker_path__, kind, args, kwargs)
+ action = Action(kind, args, kwargs, self.__mocker_path__)
path = self.__mocker_path__ + action
+ if object is not None:
+ path.root_object = object
try:
return self.__mocker__.act(path)
- except UnexpectedExprError:
+ except MatchError, exception:
+ if (self.__mocker_type__ is not None and
+ kind == "getattr" and args == ("__class__",)):
+ return self.__mocker_type__
root_mock = path.root_mock
- if (root_mock.__mocker_passthrough__ and
- root_mock.__mocker_object__ is not None):
- return path.apply(root_mock.__mocker_object__)
- raise
+ if (path.root_object is not None and
+ root_mock.__mocker_passthrough__):
+ return path.execute(path.root_object)
+ # Reinstantiate to show raise statement on traceback, and
+ # also to make it shorter.
+ raise MatchError(str(exception))
+ except AssertionError, e:
+ lines = str(e).splitlines()
+ message = [ERROR_PREFIX + "Unmet expectation:", ""]
+ message.append("=> " + lines.pop(0))
+ message.extend(" " + line for line in lines)
+ message.append("")
+ raise AssertionError(os.linesep.join(message))
def __getattribute__(self, name):
if name.startswith("__mocker_"):
return super(Mock, self).__getattribute__(name)
- return self.__mocker_act__("getattr", name)
+ return self.__mocker_act__("getattr", (name,))
def __call__(self, *args, **kwargs):
- return self.__mocker_act__("call", *args, **kwargs)
+ return self.__mocker_act__("call", args, kwargs)
def find_object_name(obj, depth=0):
@@ -364,35 +718,62 @@ def find_object_name(obj, depth=0):
class Action(object):
- def __init__(self, path, kind, args, kwargs):
- self.path = path
+ def __init__(self, kind, args, kwargs, path=None):
self.kind = kind
self.args = args
self.kwargs = kwargs
- self._apply_cache = {}
+ self.path = path
+ self._execute_cache = {}
- def apply(self, object):
+ def __repr__(self):
+ if self.path is None:
+ return "Action(%r, %r, %r)" % (self.kind, self.args, self.kwargs)
+ return "Action(%r, %r, %r, %r)" % \
+ (self.kind, self.args, self.kwargs, self.path)
+
+ def __eq__(self, other):
+ return (self.kind == other.kind and
+ self.args == other.args and
+ self.kwargs == other.kwargs)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def matches(self, other):
+ return (self.kind == other.kind and
+ match_params(self.args, self.kwargs, other.args, other.kwargs))
+
+ def execute(self, object):
# This caching scheme may fail if the object gets deallocated before
# the action, as the id might get reused. It's somewhat easy to fix
# that with a weakref callback. For our uses, though, the object
# should never get deallocated before the action itself, so we'll
# just keep it simple.
- if id(object) in self._apply_cache:
- return self._apply_cache[id(object)]
- kind = self.kind
- if kind == "getattr":
- kind = "getattribute"
- method = getattr(object, "__%s__" % kind)
- result = method(*self.args, **self.kwargs)
- self._apply_cache[id(object)] = result
+ if id(object) in self._execute_cache:
+ return self._execute_cache[id(object)]
+ execute = getattr(object, "__mocker_execute__", None)
+ if execute is not None:
+ result = execute(self, object)
+ else:
+ kind = self.kind
+ if kind == "getattr":
+ result = getattr(object, self.args[0])
+ elif kind == "call":
+ result = object(*self.args, **self.kwargs)
+ else:
+ raise RuntimeError("Don't know how to apply %r kind"
+ % self.kind)
+ self._execute_cache[id(object)] = result
return result
class Path(object):
- def __init__(self, root_mock, actions=()):
+ def __init__(self, root_mock, root_object=None, actions=()):
self.root_mock = root_mock
+ self.root_object = root_object
self.actions = tuple(actions)
+ self.__mocker_replace__ = False
@property
def parent_path(self):
@@ -402,7 +783,8 @@ class Path(object):
def __add__(self, action):
"""Return a new path which includes the given action at the end."""
- return self.__class__(self.root_mock, self.actions + (action,))
+ return self.__class__(self.root_mock, self.root_object,
+ self.actions + (action,))
def __eq__(self, other):
"""Verify if the two paths are equal.
@@ -411,12 +793,11 @@ class Path(object):
have the actions with equal kind, args and kwargs.
"""
if (self.root_mock is not other.root_mock or
+ self.root_object is not other.root_object or
len(self.actions) != len(other.actions)):
return False
for action, other_action in zip(self.actions, other.actions):
- if (action.kind != other_action.kind or
- action.args != other_action.args or
- action.kwargs != other_action.kwargs):
+ if action != other_action:
return False
return True
@@ -430,17 +811,15 @@ class Path(object):
len(self.actions) != len(other.actions)):
return False
for action, other_action in zip(self.actions, other.actions):
- if (action.kind != other_action.kind or
- not match_params(action.args, action.kwargs,
- other_action.args, other_action.kwargs)):
+ if not action.matches(other_action):
return False
return True
- def apply(self, object):
- """Apply all actions sequentially on object, and return result.
+ def execute(self, object):
+ """Execute all actions sequentially on object, and return result.
"""
for action in self.actions:
- object = action.apply(object)
+ object = action.execute(object)
return object
def __str__(self):
@@ -461,66 +840,114 @@ class Path(object):
class SpecialArgument(object):
- """Markup base for special arguments for matching parameters."""
+ """Base for special arguments for matching parameters."""
+ def __init__(self, object=None):
+ self.object = object
-class ANY(SpecialArgument):
- def __eq__(self, other):
- return not isinstance(other, SpecialArgument) or self is other
def __repr__(self):
- return "ANY"
+ if self.object is None:
+ return self.__class__.__name__
+ else:
+ return "%s(%r)" % (self.__class__.__name__, self.object)
+
+ def matches(self, other):
+ return True
+
+ def __eq__(self, other):
+ return type(other) == type(self) and self.object == other.object
+
+
+class ANY(SpecialArgument):
+ """Matches any single argument."""
+
ANY = ANY()
class VARIOUS(SpecialArgument):
- def __repr__(self):
- return "VARIOUS"
- def __eq__(self, other):
- return self is other
+ """Matches zero or more arguments."""
+
VARIOUS = VARIOUS()
+class ARGS(SpecialArgument):
+ """Matches zero or more positional arguments."""
+
+ARGS = ARGS()
+
+
+class KWARGS(SpecialArgument):
+ """Matches zero or more keyword arguments."""
+
+KWARGS = KWARGS()
+
+
class SAME(SpecialArgument):
- def __init__(self, object):
- self.object = object
- def __repr__(self):
- return "SAME(%r)" % (self.object,)
- def __eq__(self, other):
- if isinstance(other, SpecialArgument):
- return type(other) == type(self) and self.object is other.object
+
+ def matches(self, other):
return self.object is other
+ def __eq__(self, other):
+ return type(other) == type(self) and self.object is other.object
+
class CONTAINS(SpecialArgument):
- def __init__(self, object):
- self.object = object
- def __repr__(self):
- return "CONTAINS(%r)" % (self.object,)
- def __eq__(self, other):
- if isinstance(other, SpecialArgument):
- return type(other) == type(self) and self.object == other.object
+
+ def matches(self, other):
+ try:
+ other.__contains__
+ except AttributeError:
+ try:
+ iter(other)
+ except TypeError:
+ # If an object can't be iterated, and has no __contains__
+ # hook, it'd blow up on the test below. We test this in
+ # advance to prevent catching more errors than we really
+ # want.
+ return False
return self.object in other
def match_params(args1, kwargs1, args2, kwargs2):
"""Match the two sets of parameters, considering the special VARIOUS."""
- # If they are equal, we're done.
- if args1 == args2 and kwargs1 == kwargs2:
- return True
+ has_args = ARGS in args1
+ has_kwargs = KWARGS in args1
+
+ if has_kwargs:
+ args1 = [arg1 for arg1 in args1 if arg1 is not KWARGS]
+ elif len(kwargs1) != len(kwargs2):
+ return False
- # Then, only if we have a VARIOUS argument we have a chance of matching.
- if VARIOUS not in args1:
+ if not has_args and len(args1) != len(args2):
return False
- # Any keyword requests should be honored, but unrequested keywords
- # are also accepted, since the user is fine with whatever (VARIOUS!).
- for key, value in kwargs1.iteritems():
- if kwargs2.get(key) != value:
+ # Either we have the same number of kwargs, or unknown keywords are
+ # accepted (KWARGS was used), so check just the ones in kwargs1.
+ for key, arg1 in kwargs1.iteritems():
+ if key not in kwargs2:
return False
+ arg2 = kwargs2[key]
+ if isinstance(arg1, SpecialArgument):
+ if not arg1.matches(arg2):
+ return False
+ elif arg1 != arg2:
+ return False
+
+ # Keywords match. Now either we have the same number of
+ # arguments, or ARGS was used. If ARGS wasn't used, arguments
+ # must match one-on-one necessarily.
+ if not has_args:
+ for arg1, arg2 in zip(args1, args2):
+ if isinstance(arg1, SpecialArgument):
+ if not arg1.matches(arg2):
+ return False
+ elif arg1 != arg2:
+ return False
+ return True
- # Easy choice. Keywords are matching, and anything on args are accepted.
- if (VARIOUS,) == args1:
+ # Easy choice. Keywords are matching, and anything on args is accepted.
+ if (ARGS,) == args1:
return True
# We have something different there. If we don't have positional
@@ -528,7 +955,7 @@ def match_params(args1, kwargs1, args2, kwargs2):
if not args2:
# Unless we have just several VARIOUS (which is bizarre, but..).
for arg1 in args1:
- if arg1 is not VARIOUS:
+ if arg1 is not ARGS:
return False
return True
@@ -536,14 +963,14 @@ def match_params(args1, kwargs1, args2, kwargs2):
# matching. This is an algorithm based on the idea of the Levenshtein
# Distance between two strings, but heavily hacked for this purpose.
args2l = len(args2)
- if args1[0] is VARIOUS:
+ if args1[0] is ARGS:
args1 = args1[1:]
array = [0]*args2l
else:
array = [1]*args2l
for i in range(len(args1)):
last = array[0]
- if args1[i] is VARIOUS:
+ if args1[i] is ARGS:
for j in range(1, args2l):
last, array[j] = array[j], min(array[j-1], array[j], last)
else:
@@ -604,10 +1031,25 @@ class Event(object):
which isn't None, or None if they're all None.
"""
result = None
+ errors = []
for task in self._tasks:
- task_result = task.run(path)
- if task_result is not None:
- result = task_result
+ try:
+ task_result = task.run(path)
+ except AssertionError, e:
+ error = str(e)
+ if not error:
+ raise RuntimeError("Empty error message from %r" % task)
+ errors.append(error)
+ else:
+ if task_result is not None:
+ result = task_result
+ if errors:
+ message = [str(self.path)]
+ for error in errors:
+ lines = error.splitlines()
+ message.append("- " + lines.pop(0))
+ message.extend(" " + line for line in lines)
+ raise AssertionError(os.linesep.join(message))
return result
def satisfied(self):
@@ -615,21 +1057,37 @@ class Event(object):
Being satisfied means that there are no unmet expectations.
"""
- try:
- self.verify()
- except AssertionError:
- return False
+ for task in self._tasks:
+ try:
+ task.verify()
+ except AssertionError:
+ return False
return True
def verify(self):
"""Run verify on all tasks.
The verify method is supposed to raise an AssertionError if the
- task has unmet expectations, with a nice debugging message
- explaining why it wasn't met.
+ task has unmet expectations, with a one-line explanation about
+ why this item is unmet. This method should be safe to be called
+ multiple times without side effects.
"""
+ errors = []
for task in self._tasks:
- task.verify()
+ try:
+ task.verify()
+ except AssertionError, e:
+ error = str(e)
+ if not error:
+ raise RuntimeError("Empty error message from %r" % task)
+ errors.append(error)
+ if errors:
+ message = [str(self.path)]
+ for error in errors:
+ lines = error.splitlines()
+ message.append("- " + lines.pop(0))
+ message.extend(" " + line for line in lines)
+ raise AssertionError(os.linesep.join(message))
def set_state(self, state):
"""Change the task state of all tasks to reflect that of the mocker.
@@ -660,8 +1118,10 @@ class Task(object):
def verify(self):
"""Raise AssertionError if expectations for this item are unmet.
- The exception should include a nice explanation about why this
- item is unmet.
+ The verify method is supposed to raise an AssertionError if the
+ task has unmet expectations, with a one-line explanation about
+ why this item is unmet. This method should be safe to be called
+ multiple times without side effects.
"""
def set_state(self, state):
@@ -784,11 +1244,11 @@ class FunctionRunner(Task):
return self._func(*action.args, **action.kwargs)
-class PathApplier(Task):
- """Task that applies a path in the real object, and returns the result."""
+class PathExecuter(Task):
+ """Task that executes a path in the real object, and returns the result."""
def run(self, path):
- return path.apply(path.root_mock.__mocker_object__)
+ return path.execute(path.root_object)
class Orderer(Task):
@@ -838,25 +1298,30 @@ class SpecChecker(Task):
def get_method(self):
return self._method
+ def _raise(self, message):
+ spec = inspect.formatargspec(self._args, self._varargs,
+ self._varkwargs, self._defaults)
+ raise AssertionError("Specification is %s%s: %s" %
+ (self._method.__name__, spec, message))
+
def run(self, path):
if not self._method:
- raise AssertionError("method not existent in real class")
+ raise AssertionError("Method not found in real specification")
action = path.actions[-1]
obtained_len = len(action.args)
obtained_kwargs = action.kwargs.copy()
nodefaults_len = len(self._args) - len(self._defaults)
for i, name in enumerate(self._args):
if i < obtained_len and name in action.kwargs:
- raise AssertionError("%r parameter provided twice" % name)
+ self._raise("%r provided twice" % name)
if (i >= obtained_len and i < nodefaults_len and
name not in action.kwargs):
- raise AssertionError("%r parameter not provided" % name)
+ self._raise("%r not provided" % name)
obtained_kwargs.pop(name, None)
if obtained_len > len(self._args) and not self._varargs:
- raise AssertionError("maximum number of parameters exceeded")
+ self._raise("too many args provided")
if obtained_kwargs and not self._varkwargs:
- raise AssertionError("unknown kwargs: %s" %
- ", ".join(obtained_kwargs))
+ self._raise("unknown kwargs: %s" % ", ".join(obtained_kwargs))
@recorder
def spec_checker_recorder(mocker, event):
@@ -868,7 +1333,7 @@ def spec_checker_recorder(mocker, event):
event.add_task(SpecChecker(method))
-class ProxyInstaller(Task):
+class ProxyReplacer(Task):
"""Task which installs and deinstalls proxy mocks.
This task will replace a real object by a mock in all dictionaries
@@ -877,19 +1342,147 @@ class ProxyInstaller(Task):
def __init__(self, mock):
self.mock = mock
+ self.__mocker_replace__ = False
+
+ def matches(self, path):
+ return False
+
+ def set_state(self, state):
+ if state is REPLAY:
+ global_replace(self.mock.__mocker_object__, self.mock)
+ else:
+ global_replace(self.mock, self.mock.__mocker_object__)
+
+
+def global_replace(remove, install):
+ """Replace object 'remove' with object 'install' on all dictionaries."""
+ for referrer in gc.get_referrers(remove):
+ if (type(referrer) is dict and
+ referrer.get("__mocker_replace__", True)):
+ for key, value in referrer.iteritems():
+ if value is remove:
+ referrer[key] = install
+
+
+class Undefined(object):
+
+ def __repr__(self):
+ return "Undefined"
+
+Undefined = Undefined()
+
+
+class Patcher(Task):
+
+ def __init__(self):
+ super(Patcher, self).__init__()
+ self._monitored = {} # {kind: {id(object): object}}
+ self._patched = {}
def matches(self, path):
return False
+ def is_monitoring(self, obj, kind):
+ monitored = self._monitored.get(kind)
+ if monitored:
+ if id(obj) in monitored:
+ return True
+ cls = type(obj)
+ if issubclass(cls, type):
+ cls = obj
+ bases = set(id(base) for base in cls.__mro__)
+ bases.intersection_update(monitored)
+ return bool(bases)
+ return False
+
+ def monitor(self, obj, kind):
+ if kind not in self._monitored:
+ self._monitored[kind] = {}
+ self._monitored[kind][id(obj)] = obj
+
+ def patch_attr(self, obj, attr, value):
+ original = obj.__dict__.get(attr, Undefined)
+ self._patched[id(obj), attr] = obj, attr, original
+ setattr(obj, attr, value)
+
+ def get_unpatched_attr(self, obj, attr):
+ cls = type(obj)
+ if issubclass(cls, type):
+ cls = obj
+ result = Undefined
+ for mro_cls in cls.__mro__:
+ key = (id(mro_cls), attr)
+ if key in self._patched:
+ result = self._patched[key][2]
+ if result is not Undefined:
+ break
+ elif attr in mro_cls.__dict__:
+ result = mro_cls.__dict__.get(attr, Undefined)
+ break
+ if isinstance(result, object) and hasattr(type(result), "__get__"):
+ if cls is obj:
+ obj = None
+ return result.__get__(obj, cls)
+ return result
+
+ def _get_kind_attr(self, kind):
+ if kind == "getattr":
+ return "__getattribute__"
+ return "__%s__" % kind
+
def set_state(self, state):
if state is REPLAY:
- install, remove = self.mock, self.mock.__mocker_object__
+ for kind in self._monitored:
+ attr = self._get_kind_attr(kind)
+ seen = set()
+ for obj in self._monitored[kind].itervalues():
+ cls = type(obj)
+ if issubclass(cls, type):
+ cls = obj
+ if cls not in seen:
+ seen.add(cls)
+ unpatched = getattr(cls, attr, Undefined)
+ self.patch_attr(cls, attr,
+ PatchedMethod(kind, unpatched,
+ self.is_monitoring))
+ self.patch_attr(cls, "__mocker_execute__",
+ self.execute)
else:
- install, remove = self.mock.__mocker_object__, self.mock
- mock_dict = object.__getattribute__(self.mock, "__dict__")
- protected = set((id(self.__dict__), id(mock_dict)))
- for referrer in gc.get_referrers(remove):
- if id(referrer) not in protected and type(referrer) is dict:
- for key, value in referrer.iteritems():
- if value is remove:
- referrer[key] = install
+ for obj, attr, original in self._patched.itervalues():
+ if original is Undefined:
+ delattr(obj, attr)
+ else:
+ setattr(obj, attr, original)
+ self._patched.clear()
+
+ def execute(self, action, object):
+ attr = self._get_kind_attr(action.kind)
+ unpatched = self.get_unpatched_attr(object, attr)
+ return unpatched(*action.args, **action.kwargs)
+
+
+class PatchedMethod(object):
+
+ def __init__(self, kind, unpatched, is_monitoring):
+ self._kind = kind
+ self._unpatched = unpatched
+ self._is_monitoring = is_monitoring
+
+ def __get__(self, obj, cls=None):
+ object = obj or cls
+ if not self._is_monitoring(object, self._kind):
+ return self._unpatched.__get__(obj, cls)
+ def method(*args, **kwargs):
+ if self._kind == "getattr" and args[0].startswith("__mocker_"):
+ return self._unpatched.__get__(obj, cls)(args[0])
+ mock = object.__mocker_mock__
+ return mock.__mocker_act__(self._kind, args, kwargs, object)
+ return method
+
+
+@recorder
+def patcher_recorder(mocker, event):
+ mock = event.path.root_mock
+ if mock.__mocker_patcher__ and len(event.path.actions) == 1:
+ patcher = mock.__mocker_patcher__
+ patcher.monitor(mock.__mocker_object__, event.path.actions[0].kind)
diff --git a/test.py b/test.py
index f51edb9..d5fcd7b 100755
--- a/test.py
+++ b/test.py
@@ -1,6 +1,7 @@
#!/usr/bin/python
import unittest
import sys
+import os
import gc
from types import ModuleType
@@ -11,7 +12,8 @@ from mocker import (
run_counter_recorder, run_counter_removal_recorder, MockReturner,
mock_returner_recorder, FunctionRunner, Orderer, SpecChecker,
spec_checker_recorder, match_params, ANY, VARIOUS, SAME, CONTAINS,
- UnexpectedExprError, PathApplier, RECORD, REPLAY, RESTORE, ProxyInstaller)
+ ARGS, KWARGS, MatchError, PathExecuter, RECORD, REPLAY, RESTORE,
+ ProxyReplacer, Patcher, Undefined, PatchedMethod)
class CleanMocker(MockerBase):
@@ -24,7 +26,7 @@ class IntegrationTest(unittest.TestCase):
self.mocker = Mocker()
def test_count(self):
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
obj.x
self.mocker.count(2, 3)
@@ -38,7 +40,7 @@ class IntegrationTest(unittest.TestCase):
self.assertRaises(AssertionError, getattr, obj, "x")
def test_ordered(self):
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
with_manager = self.mocker.ordered()
with_manager.__enter__()
@@ -56,11 +58,11 @@ class IntegrationTest(unittest.TestCase):
obj.y
obj.z
- def test_spec(self):
+ def test_spec_and_type(self):
class C(object):
def m(self, a): pass
- obj = self.mocker.obj(C)
+ obj = self.mocker.mock(C)
obj.m(1)
obj.m(a=1)
@@ -73,6 +75,8 @@ class IntegrationTest(unittest.TestCase):
self.mocker.replay()
+ self.assertTrue(isinstance(obj, C))
+
obj.m(1)
obj.m(a=1)
obj.y()
@@ -82,14 +86,14 @@ class IntegrationTest(unittest.TestCase):
self.assertRaises(AssertionError, obj.z)
def test_result(self):
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
obj.x
self.mocker.result(42)
self.mocker.replay()
self.assertEquals(obj.x, 42)
def test_throw(self):
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
obj.x()
self.mocker.throw(ValueError)
self.mocker.replay()
@@ -100,7 +104,7 @@ class IntegrationTest(unittest.TestCase):
def func(arg):
calls.append(arg)
return 42
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
obj.x(24)
self.mocker.call(func)
self.mocker.replay()
@@ -112,7 +116,7 @@ class IntegrationTest(unittest.TestCase):
def func(arg):
calls.append(arg)
return arg
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
obj.x(24)
self.mocker.call(func)
self.mocker.result(42)
@@ -126,7 +130,7 @@ class IntegrationTest(unittest.TestCase):
return sum(args)
obj = self.mocker.proxy(C())
- expect(obj.multiply(2, 3)).result(6)
+ expect(obj.multiply(2, 3)).result(6).nospec()
expect(obj.sum(0, 0)).result(1)
expect(obj.sum(0, 0)).passthrough()
@@ -140,9 +144,9 @@ class IntegrationTest(unittest.TestCase):
self.assertEquals(obj.sum(0, 0), 0) # Passed through explicitly.
self.assertRaises(AssertionError, obj.sum, 0, 0) # Seen twice.
- def test_module_install_and_restore(self):
+ def test_replace_install_and_restore(self):
try:
- module = self.mocker.module("calendar")
+ module = self.mocker.replace("calendar")
import calendar
self.assertTrue(calendar is not module)
self.mocker.replay()
@@ -154,11 +158,11 @@ class IntegrationTest(unittest.TestCase):
finally:
self.mocker.restore()
- def test_module_os_path(self):
+ def test_replace_os_path_join(self):
try:
- path = self.mocker.module("os.path")
- expect(path.join(VARIOUS)).call(lambda *args: "-".join(args))
- expect(path.join("e", VARIOUS)).passthrough()
+ path = self.mocker.replace("os.path")
+ expect(path.join(ARGS)).call(lambda *args: "-".join(args))
+ expect(path.join("e", ARGS)).passthrough()
self.mocker.replay()
import os
self.assertEquals(os.path.join("a", "b", "c"), "a-b-c")
@@ -166,6 +170,18 @@ class IntegrationTest(unittest.TestCase):
finally:
self.mocker.restore()
+ def test_replace_os_path_isfile(self):
+ try:
+ path = self.mocker.replace("os.path")
+ expect(path.isfile("unexistent")).result(True)
+ expect(path.isfile(ANY)).passthrough().count(2)
+ self.mocker.replay()
+ import os
+ self.assertFalse(os.path.isfile("another-unexistent"))
+ self.assertTrue(os.path.isfile("unexistent"))
+ self.assertFalse(os.path.isfile("unexistent"))
+ finally:
+ self.mocker.restore()
class ExpectTest(unittest.TestCase):
@@ -173,13 +189,13 @@ class ExpectTest(unittest.TestCase):
self.mocker = CleanMocker()
def test_calling_mocker(self):
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
expect(obj.attr).result(123)
self.mocker.replay()
self.assertEquals(obj.attr, 123)
def test_chaining(self):
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
expect(obj.attr).result(123).result(42)
self.mocker.replay()
self.assertEquals(obj.attr, 42)
@@ -194,8 +210,8 @@ class MockerTest(unittest.TestCase):
def recorder(mocker, event):
self.recorded.append((mocker, event))
- self.action = Action(Path(Mock(self.mocker, name="mock")),
- "getattr", ("attr",), {})
+ self.action = Action("getattr", ("attr",), {},
+ Path(Mock(self.mocker, name="mock")))
self.path = self.action.path + self.action
def test_default_state(self):
@@ -233,18 +249,77 @@ class MockerTest(unittest.TestCase):
self.assertEquals(calls, [RESTORE])
def test_verify(self):
- calls = []
class MyEvent(object):
- def __init__(self, name):
- self.name = name
+ def __init__(self, id, failed):
+ self.id = id
+ self.failed = failed
def verify(self):
- calls.append(self.name)
- self.mocker.add_event(MyEvent("1"))
- self.mocker.add_event(MyEvent("2"))
+ if self.failed:
+ raise AssertionError("%d failed\n- Line 1\n- Line 2\n"
+ % self.id)
- self.mocker.verify()
+ self.mocker.add_event(MyEvent(1, True))
+ self.mocker.add_event(MyEvent(2, False))
+ self.mocker.add_event(MyEvent(3, True))
+
+ try:
+ self.mocker.verify()
+ except AssertionError, e:
+ message = os.linesep.join(["[Mocker] Unmet expectations:",
+ "",
+ "=> 1 failed",
+ " - Line 1",
+ " - Line 2",
+ "",
+ "=> 3 failed",
+ " - Line 1",
+ " - Line 2",
+ ""])
+ self.assertEquals(str(e), message)
+ else:
+ self.fail("AssertionError not raised")
+
+ def test_mocker_as_context_manager(self):
+ calls = []
+ throw = False
+ class MyEvent(Event):
+ def verify(self):
+ calls.append("verify")
+ if throw:
+ raise AssertionError("Some problem")
+ def set_state(self, state):
+ calls.append({REPLAY: "replay", RESTORE: "restore"}[state])
+
+ event = MyEvent()
+ self.mocker.add_event(event)
+
+ self.assertEquals(calls, [])
+
+ mocker = self.mocker.__enter__()
+ self.assertTrue(mocker is self.mocker)
+ self.assertEquals(calls, ["replay"])
+
+ # Verify without errors.
+ del calls[:]
+ result = self.mocker.__exit__(None, None, None)
+ self.assertEquals(result, False)
+ self.assertEquals(calls, ["restore", "verify"])
- self.assertEquals(calls, ["1", "2"])
+ throw = True
+
+ # Verify raising an error.
+ self.mocker.replay()
+ del calls[:]
+ self.assertRaises(AssertionError,
+ self.mocker.__exit__, None, None, None)
+ self.assertEquals(calls, ["restore", "verify"])
+
+ # An exception happened in the 'with' block. Verify won't raise.
+ self.mocker.replay()
+ del calls[:]
+ result = self.mocker.__exit__(AssertionError, None, None)
+ self.assertEquals(result, False)
+ self.assertEquals(calls, ["restore"])
def test_add_recorder_on_instance(self):
obj1 = object()
@@ -305,72 +380,136 @@ class MockerTest(unittest.TestCase):
MyMocker.remove_recorder(obj1)
self.assertEquals(MyMocker.get_recorders(), [obj2])
- def test_obj(self):
- self.mocker = CleanMocker()
- obj = self.mocker.obj()
- self.assertEquals(type(obj), Mock)
+ def test_mock_with_name(self):
+ mock = self.mocker.mock(name="name")
+ self.assertEquals(mock.__mocker_name__, "name")
- def test_obj_with_spec(self):
+ def test_mock_with_spec(self):
class C(object): pass
- self.mocker = CleanMocker()
- obj = self.mocker.obj(C)
- self.assertEquals(obj.__mocker_spec__, C)
+ mock = self.mocker.mock(spec=C)
+ self.assertEquals(mock.__mocker_spec__, C)
+
+ def test_mock_with_type(self):
+ class C(object): pass
+ mock = self.mocker.mock(type=C)
+ self.assertEquals(mock.__mocker_type__, C)
+
+ def test_mock_with_spec_and_type(self):
+ class C(object): pass
+ mock = self.mocker.mock(C)
+ self.assertEquals(mock.__mocker_spec__, C)
+ self.assertEquals(mock.__mocker_type__, C)
def test_proxy(self):
original = object()
- self.mocker = CleanMocker()
- obj = self.mocker.proxy(original)
- self.assertEquals(type(obj), Mock)
- self.assertEquals(obj.__mocker_object__, original)
+ mock = self.mocker.proxy(original)
+ self.assertEquals(type(mock), Mock)
+ self.assertEquals(mock.__mocker_object__, original)
+ self.assertEquals(mock.__mocker_path__.root_object, original)
def test_proxy_with_spec(self):
original = object()
class C(object): pass
- self.mocker = CleanMocker()
- obj = self.mocker.proxy(original, C)
- self.assertEquals(obj.__mocker_object__, original)
- self.assertEquals(obj.__mocker_spec__, C)
+ mock = self.mocker.proxy(original, C)
+ self.assertEquals(mock.__mocker_object__, original)
+ self.assertEquals(mock.__mocker_spec__, C)
+
+ def test_proxy_with_type(self):
+ original = object()
+ class C(object): pass
+ mock = self.mocker.proxy(original, type=C)
+ self.assertEquals(mock.__mocker_type__, C)
+
+ def test_proxy_spec_defaults_to_the_object_itself(self):
+ original = object()
+ mock = self.mocker.proxy(original)
+ self.assertEquals(mock.__mocker_spec__, original)
+
+ def test_proxy_type_defaults_to_the_object_type(self):
+ original = object()
+ mock = self.mocker.proxy(original)
+ self.assertEquals(mock.__mocker_type__, object)
+
+ def test_proxy_with_spec_and_type_none(self):
+ original = object()
+ mock = self.mocker.proxy(original, spec=None, type=None)
+ self.assertEquals(mock.__mocker_spec__, None)
+ self.assertEquals(mock.__mocker_type__, None)
def test_proxy_with_passthrough_false(self):
original = object()
class C(object): pass
- self.mocker = CleanMocker()
- obj = self.mocker.proxy(original, C, passthrough=False)
- self.assertEquals(obj.__mocker_object__, original)
- self.assertEquals(obj.__mocker_spec__, C)
- self.assertEquals(obj.__mocker_passthrough__, False)
+ mock = self.mocker.proxy(original, C, passthrough=False)
+ self.assertEquals(mock.__mocker_object__, original)
+ self.assertEquals(mock.__mocker_spec__, C)
+ self.assertEquals(mock.__mocker_passthrough__, False)
- def test_proxy_install(self):
+ def test_replace(self):
from os import path
obj = object()
- proxy = self.mocker.proxy(obj, install=True)
+ proxy = self.mocker.replace(obj, spec=object, name="obj",
+ passthrough=False)
self.assertEquals(type(proxy), Mock)
self.assertEquals(type(proxy.__mocker_object__), object)
self.assertEquals(proxy.__mocker_object__, obj)
+ self.assertEquals(proxy.__mocker_spec__, object)
+ self.assertEquals(proxy.__mocker_name__, "obj")
(event,) = self.mocker.get_events()
(task,) = event.get_tasks()
- self.assertEquals(type(task), ProxyInstaller)
+ self.assertEquals(type(task), ProxyReplacer)
self.assertTrue(task.mock is proxy)
self.assertTrue(task.mock.__mocker_object__ is obj)
self.assertTrue(proxy is not obj)
- def test_module(self):
+ def test_replace_with_submodule_string(self):
from os import path
- module = self.mocker.module("os.path")
+ module = self.mocker.replace("os.path")
self.assertEquals(type(module), Mock)
self.assertEquals(type(module.__mocker_object__), ModuleType)
self.assertEquals(module.__mocker_name__, "os.path")
self.assertEquals(module.__mocker_object__, path)
(event,) = self.mocker.get_events()
(task,) = event.get_tasks()
- self.assertEquals(type(task), ProxyInstaller)
+ self.assertEquals(type(task), ProxyReplacer)
self.assertTrue(task.mock is module)
self.assertTrue(task.mock.__mocker_object__ is path)
self.assertTrue(module is not path)
- def test_module_with_passthrough_false(self):
- module = self.mocker.module("calendar", passthrough=False)
- self.assertEquals(module.__mocker_passthrough__, False)
+ def test_replace_with_module_function_string(self):
+ mock = self.mocker.replace("os.path.join.func_name")
+ self.assertEquals(mock.__mocker_object__, "join")
+
+ def test_replace_with_string_and_name(self):
+ module = self.mocker.replace("os.path", name="mock")
+ self.assertEquals(module.__mocker_name__, "mock")
+
+ def test_replace_with_type(self):
+ original = object()
+ class C(object): pass
+ mock = self.mocker.replace(original, type=C)
+ self.assertEquals(mock.__mocker_type__, C)
+
+ def test_replace_spec_defaults_to_the_object_itself(self):
+ original = object()
+ mock = self.mocker.replace(original)
+ self.assertEquals(mock.__mocker_spec__, original)
+
+ def test_replace_type_defaults_to_the_object_type(self):
+ original = object()
+ mock = self.mocker.replace(original)
+ self.assertEquals(mock.__mocker_type__, object)
+
+ def test_replace_with_spec_and_type_none(self):
+ original = object()
+ mock = self.mocker.replace(original, spec=None, type=None)
+ self.assertEquals(mock.__mocker_spec__, None)
+ self.assertEquals(mock.__mocker_type__, None)
+
+ def test_replace_with_passthrough_false(self):
+ original = object()
+ class C(object): pass
+ mock = self.mocker.replace(original, passthrough=False)
+ self.assertEquals(mock.__mocker_passthrough__, False)
def test_add_and_get_event(self):
self.mocker.add_event(41)
@@ -378,31 +517,32 @@ class MockerTest(unittest.TestCase):
self.assertEquals(self.mocker.get_events(), [41, 42])
def test_recording(self):
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
obj.attr()
self.assertEquals(len(self.recorded), 2)
- action1 = Action(None, "getattr", ("attr",), {})
- action2 = Action(None, "call", (), {})
+ action1 = Action("getattr", ("attr",), {})
+ action2 = Action("call", (), {})
mocker1, event1 = self.recorded[0]
self.assertEquals(mocker1, self.mocker)
self.assertEquals(type(event1), Event)
- self.assertTrue(event1.path.matches(Path(obj, [action1])))
+ self.assertTrue(event1.path.matches(Path(obj, None, [action1])))
mocker2, event2 = self.recorded[1]
self.assertEquals(mocker2, self.mocker)
self.assertEquals(type(event2), Event)
- self.assertTrue(event2.path.matches(Path(obj, [action1, action2])))
+ self.assertTrue(event2.path.matches(Path(obj, None,
+ [action1, action2])))
self.assertEquals(self.mocker.get_events(), [event1, event2])
def test_recording_result_path(self):
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
result = obj.attr()
- path = Path(obj, [Action(None, "getattr", ("attr",), {}),
- Action(None, "call", (), {})])
+ path = Path(obj, None, [Action("getattr", ("attr",), {}),
+ Action("call", (), {})])
self.assertTrue(result.__mocker_path__.matches(path))
def test_replaying_no_events(self):
@@ -456,7 +596,7 @@ class MockerTest(unittest.TestCase):
def verify(self):
if not self.raised:
self.raised = True
- raise AssertionError()
+ raise AssertionError("An error")
def run(self, path):
return "result2"
event1 = self.mocker.add_event(Event())
@@ -515,10 +655,10 @@ class MockerTest(unittest.TestCase):
self.assertEquals(task.max, 4)
def test_order(self):
- mock1 = self.mocker.obj()
- mock2 = self.mocker.obj()
- mock3 = self.mocker.obj()
- mock4 = self.mocker.obj()
+ mock1 = self.mocker.mock()
+ mock2 = self.mocker.mock()
+ mock3 = self.mocker.mock()
+ mock4 = self.mocker.mock()
result1 = mock1.attr1(1)
result2 = mock2.attr2(2)
result3 = mock3.attr3(3)
@@ -560,9 +700,9 @@ class MockerTest(unittest.TestCase):
self.assertEquals(other_task_, other_task)
def test_after(self):
- mock1 = self.mocker.obj()
- mock2 = self.mocker.obj()
- mock3 = self.mocker.obj()
+ mock1 = self.mocker.mock()
+ mock2 = self.mocker.mock()
+ mock3 = self.mocker.mock()
result1 = mock1.attr1(1)
result2 = mock2.attr2(2)
result3 = mock3.attr3(3)
@@ -596,9 +736,9 @@ class MockerTest(unittest.TestCase):
self.assertEquals(other_task_, other_task)
def test_before(self):
- mock1 = self.mocker.obj()
- mock2 = self.mocker.obj()
- mock3 = self.mocker.obj()
+ mock1 = self.mocker.mock()
+ mock2 = self.mocker.mock()
+ mock3 = self.mocker.mock()
result1 = mock1.attr1(1)
result2 = mock2.attr2(2)
result3 = mock3.attr3(3)
@@ -638,7 +778,7 @@ class MockerTest(unittest.TestCase):
self.mocker.ordered()
self.assertEquals(self.mocker.is_ordering(), True)
- def test_ordered_enter_exit(self):
+ def test_ordered_context_manager(self):
with_manager = self.mocker.ordered()
self.assertEquals(self.mocker.is_ordering(), True)
with_manager.__enter__()
@@ -652,7 +792,7 @@ class MockerTest(unittest.TestCase):
self.assertEquals(self.mocker.is_ordering(), False)
def test_ordered_events(self):
- mock = self.mocker.obj()
+ mock = self.mocker.mock()
# Ensure that the state is correctly reset between
# different ordered blocks.
@@ -691,26 +831,39 @@ class MockerTest(unittest.TestCase):
self.assertEquals(event2.get_tasks(), [task2, task4])
def test_passthrough(self):
- obj = self.mocker.proxy(object())
- event1 = self.mocker.add_event(Event(Path(obj)))
- event2 = self.mocker.add_event(Event(Path(obj)))
+ obj = object()
+ mock = self.mocker.proxy(obj)
+ event1 = self.mocker.add_event(Event(Path(mock, obj)))
+ event2 = self.mocker.add_event(Event(Path(mock, obj)))
self.mocker.passthrough()
self.assertEquals(event1.get_tasks(), [])
(task,) = event2.get_tasks()
- self.assertEquals(type(task), PathApplier)
+ self.assertEquals(type(task), PathExecuter)
def test_passthrough_fails_on_unproxied(self):
- obj = self.mocker.obj()
- event1 = self.mocker.add_event(Event(Path(obj)))
- event2 = self.mocker.add_event(Event(Path(obj)))
+ mock = self.mocker.mock()
+ event1 = self.mocker.add_event(Event(Path(mock)))
+ event2 = self.mocker.add_event(Event(Path(mock)))
self.assertRaises(TypeError, self.mocker.passthrough)
def test_on(self):
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
self.mocker.on(obj.attr).result(123)
self.mocker.replay()
self.assertEquals(obj.attr, 123)
+ def test_patch(self):
+ class C(object): pass
+ mock = self.mocker.patch(C)
+ self.assertEquals(type(C.__mocker_mock__), Mock)
+ self.assertTrue(C.__mocker_mock__ is mock)
+ self.assertTrue(mock.__mocker_object__ is C)
+ self.assertEquals(type(mock.__mocker_patcher__), Patcher)
+ self.assertEquals(mock.__mocker_passthrough__, True)
+ (event,) = self.mocker.get_events()
+ (task,) = event.get_tasks()
+ self.assertTrue(task is mock.__mocker_patcher__)
+
class ActionTest(unittest.TestCase):
@@ -720,32 +873,77 @@ class ActionTest(unittest.TestCase):
def test_create(self):
objects = [object() for i in range(4)]
action = Action(*objects)
- self.assertEquals(action.path, objects[0])
- self.assertEquals(action.kind, objects[1])
- self.assertEquals(action.args, objects[2])
- self.assertEquals(action.kwargs, objects[3])
+ self.assertEquals(action.kind, objects[0])
+ self.assertEquals(action.args, objects[1])
+ self.assertEquals(action.kwargs, objects[2])
+ self.assertEquals(action.path, objects[3])
- def test_apply_getattr(self):
+ def test_execute_getattr(self):
class C(object):
pass
obj = C()
obj.x = C()
- action = Action(None, "getattr", ("x",), {})
- self.assertEquals(action.apply(obj), obj.x)
+ action = Action("getattr", ("x",), {})
+ self.assertEquals(action.execute(obj), obj.x)
- def test_apply_call(self):
+ def test_execute_call(self):
obj = lambda a, b: a+b
- action = Action(None, "call", (1,), {"b": 2})
- self.assertEquals(action.apply(obj), 3)
+ action = Action("call", (1,), {"b": 2})
+ self.assertEquals(action.execute(obj), 3)
- def test_apply_caching(self):
+ def test_execute_caching(self):
values = iter(range(10))
obj = lambda: values.next()
- action = Action(None, "call", (), {})
- self.assertEquals(action.apply(obj), 0)
- self.assertEquals(action.apply(obj), 0)
+ action = Action("call", (), {})
+ self.assertEquals(action.execute(obj), 0)
+ self.assertEquals(action.execute(obj), 0)
obj = lambda: values.next()
- self.assertEquals(action.apply(obj), 1)
+ self.assertEquals(action.execute(obj), 1)
+
+ def test_equals(self):
+ obj1 = object()
+ obj2 = object()
+
+ self.assertEquals(Action("kind", (), {}, obj1),
+ Action("kind", (), {}, obj2))
+ self.assertNotEquals(Action("kind", (), {}, obj1),
+ Action("dnik", (), {}, obj2))
+ self.assertNotEquals(Action("kind", (), {}, obj1),
+ Action("kind", (1,), {}, obj2))
+ self.assertNotEquals(Action("kind", (), {}, obj1),
+ Action("kind", (), {"a": 1}, obj2))
+ self.assertNotEquals(Action("kind", (ANY,), {}, obj1),
+ Action("kind", (1,), {}, obj2))
+ self.assertEquals(Action("kind", (CONTAINS(1),), {}, obj1),
+ Action("kind", (CONTAINS(1),), {}, obj2))
+
+ def test_matches(self):
+ obj1 = object()
+ obj2 = object()
+
+ action1 = Action("kind", (), {}, obj1)
+ action2 = Action("kind", (), {}, obj2)
+ self.assertTrue(action1.matches(action2))
+
+ action1 = Action("kind", (), {}, obj1)
+ action2 = Action("dnik", (), {}, obj2)
+ self.assertFalse(action1.matches(action2))
+
+ action1 = Action("kind", (), {}, obj1)
+ action2 = Action("kind", (1,), {}, obj2)
+ self.assertFalse(action1.matches(action2))
+
+ action1 = Action("kind", (), {}, obj1)
+ action2 = Action("kind", (), {"a": 1}, obj2)
+ self.assertFalse(action1.matches(action2))
+
+ action1 = Action("kind", (ARGS,), {}, obj1)
+ action2 = Action("kind", (), {}, obj2)
+ self.assertTrue(action1.matches(action2))
+
+ action1 = Action("kind", (ARGS,), {"a": 1}, obj1)
+ action2 = Action("kind", (), {}, obj2)
+ self.assertFalse(action1.matches(action2))
class PathTest(unittest.TestCase):
@@ -757,31 +955,41 @@ class PathTest(unittest.TestCase):
pass
self.mocker = StubMocker()
self.mock = Mock(self.mocker, name="obj")
+ self.object = object()
def test_create(self):
mock = object()
path = Path(mock)
self.assertEquals(path.root_mock, mock)
+ self.assertEquals(path.root_object, None)
self.assertEquals(path.actions, ())
+ def test_create_with_object(self):
+ mock = object()
+ path = Path(mock, self.object)
+ self.assertEquals(path.root_mock, mock)
+ self.assertEquals(path.root_object, self.object)
+
def test_create_with_actions(self):
mock = object()
- path = Path(mock, [1,2,3])
+ path = Path(mock, self.object, [1,2,3])
self.assertEquals(path.root_mock, mock)
+ self.assertEquals(path.root_object, self.object)
self.assertEquals(path.actions, (1,2,3))
def test_add(self):
mock = object()
- path = Path(mock, [1,2,3])
+ path = Path(mock, self.object, [1,2,3])
result = path + 4
self.assertTrue(result is not path)
self.assertEquals(result.root_mock, mock)
+ self.assertEquals(result.root_object, self.object)
self.assertEquals(result.actions, (1,2,3,4))
def test_parent_path(self):
path1 = Path(self.mock)
- path2 = path1 + Action(path1, "getattr", ("attr",), {})
- path3 = path2 + Action(path2, "getattr", ("attr",), {})
+ path2 = path1 + Action("getattr", ("attr",), {}, path1)
+ path3 = path2 + Action("getattr", ("attr",), {}, path2)
self.assertEquals(path1.parent_path, None)
self.assertEquals(path2.parent_path, path1)
@@ -789,86 +997,97 @@ class PathTest(unittest.TestCase):
def test_equals(self):
mock = object()
+ obj = object()
obj1 = object()
obj2 = object()
# Not the *same* mock.
- path1 = Path([], [])
- path2 = Path([], [])
+ path1 = Path([], obj, [])
+ path2 = Path([], obj, [])
+ self.assertNotEquals(path1, path2)
+
+ # Not the *same* object.
+ path1 = Path(mock, [], [])
+ path2 = Path(mock, [], [])
self.assertNotEquals(path1, path2)
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(mock, [Action(obj2, "kind", (), {})])
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(mock, obj, [Action("kind", (), {}, obj2)])
self.assertEquals(path1, path2)
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(mock, [Action(obj2, "dnik", (), {})])
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(mock, obj, [Action("dnik", (), {}, obj2)])
self.assertNotEquals(path1, path2)
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(object(), [Action(obj2, "kind", (), {})])
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(object(), obj, [Action("kind", (), {}, obj2)])
self.assertNotEquals(path1, path2)
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(mock, [Action(obj2, "kind", (1,), {})])
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(mock, obj, [Action("kind", (1,), {}, obj2)])
self.assertNotEquals(path1, path2)
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(mock, [Action(obj2, "kind", (), {"a": 1})])
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(mock, obj, [Action("kind", (), {"a": 1}, obj2)])
self.assertNotEquals(path1, path2)
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(mock, [])
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(mock, obj, [])
self.assertNotEquals(path1, path2)
- path1 = Path(mock, [Action(obj1, "kind", (ANY,), {})])
- path2 = Path(mock, [Action(obj2, "kind", (1), {})])
+ path1 = Path(mock, obj, [Action("kind", (ANY,), {}, obj1)])
+ path2 = Path(mock, obj, [Action("kind", (1,), {}, obj2)])
self.assertNotEquals(path1, path2)
- path1 = Path(mock, [Action(obj1, "kind", (CONTAINS(1),), {})])
- path2 = Path(mock, [Action(obj2, "kind", (CONTAINS(1),), {})])
+ path1 = Path(mock, obj, [Action("kind", (CONTAINS(1),), {}, obj1)])
+ path2 = Path(mock, obj, [Action("kind", (CONTAINS(1),), {}, obj2)])
self.assertEquals(path1, path2)
def test_matches(self):
+ obj = object()
mock = object()
obj1 = object()
obj2 = object()
# Not the *same* mock.
- path1 = Path([], [])
- path2 = Path([], [])
+ path1 = Path([], obj, [])
+ path2 = Path([], obj, [])
self.assertFalse(path1.matches(path2))
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(mock, [Action(obj2, "kind", (), {})])
+ path1 = Path(mock, obj1, [])
+ path2 = Path(mock, obj2, [])
+ self.assertTrue(path1.matches(path2))
+
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(mock, obj, [Action("kind", (), {}, obj2)])
self.assertTrue(path1.matches(path2))
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(mock, [Action(obj2, "dnik", (), {})])
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(mock, obj, [Action("dnik", (), {}, obj2)])
self.assertFalse(path1.matches(path2))
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(object(), [Action(obj2, "kind", (), {})])
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(object(), [Action("kind", (), {}, obj2)])
self.assertFalse(path1.matches(path2))
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(mock, [Action(obj2, "kind", (1,), {})])
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(mock, obj, [Action("kind", (1,), {}, obj2)])
self.assertFalse(path1.matches(path2))
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(mock, [Action(obj2, "kind", (), {"a": 1})])
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(mock, obj, [Action("kind", (), {"a": 1}, obj2)])
self.assertFalse(path1.matches(path2))
- path1 = Path(mock, [Action(obj1, "kind", (), {})])
- path2 = Path(mock, [])
+ path1 = Path(mock, obj, [Action("kind", (), {}, obj1)])
+ path2 = Path(mock, obj, [])
self.assertFalse(path1.matches(path2))
- path1 = Path(mock, [Action(obj1, "kind", (VARIOUS,), {})])
- path2 = Path(mock, [Action(obj2, "kind", (), {})])
+ path1 = Path(mock, obj, [Action("kind", (ARGS,), {}, obj1)])
+ path2 = Path(mock, obj, [Action("kind", (), {}, obj2)])
self.assertTrue(path1.matches(path2))
- path1 = Path(mock, [Action(obj1, "kind", (VARIOUS,), {"a": 1})])
- path2 = Path(mock, [Action(obj2, "kind", (), {})])
+ path1 = Path(mock, obj, [Action("kind", (ARGS,), {"a": 1}, obj1)])
+ path2 = Path(mock, obj, [Action("kind", (), {}, obj2)])
self.assertFalse(path1.matches(path2))
def test_str(self):
@@ -887,40 +1106,40 @@ class PathTest(unittest.TestCase):
self.assertEquals(str(path), "named_mock")
def test_str_getattr(self):
- path = Path(self.mock, [Action(None, "getattr", ("attr",), {})])
+ path = Path(self.mock, None, [Action("getattr", ("attr",), {})])
self.assertEquals(str(path), "obj.attr")
- path += Action(None, "getattr", ("x",), {})
+ path += Action("getattr", ("x",), {})
self.assertEquals(str(path), "obj.attr.x")
def test_str_call(self):
- path = Path(self.mock, [Action(None, "call", (), {})])
+ path = Path(self.mock, None, [Action("call", (), {})])
self.assertEquals(str(path), "obj()")
- path = Path(self.mock,
- [Action(None, "call", (1, "2"), {"a":3,"b":"4"})])
+ path = Path(self.mock, None,
+ [Action("call", (1, "2"), {"a": 3, "b": "4"})])
self.assertEquals(str(path), "obj(1, '2', a=3, b='4')")
def test_str_getattr_call(self):
- path = Path(self.mock, [Action(None, "getattr", ("x",), {}),
- Action(None, "getattr", ("y",), {}),
- Action(None, "call", ("z",), {})])
+ path = Path(self.mock, None, [Action("getattr", ("x",), {}),
+ Action("getattr", ("y",), {}),
+ Action("call", ("z",), {})])
self.assertEquals(str(path), "obj.x.y('z')")
def test_str_raise_on_unknown(self):
- path = Path(self.mock, [Action(None, "unknown", (), {})])
+ path = Path(self.mock, None, [Action("unknown", (), {})])
self.assertRaises(RuntimeError, str, path)
- def test_apply(self):
+ def test_execute(self):
class C(object):
pass
obj = C()
obj.x = C()
obj.x.y = lambda a, b: a+b
- path = Path(self.mock, [Action(None, "getattr", ("x",), {}),
- Action(None, "getattr", ("y",), {}),
- Action(None, "call", (1,), {"b": 2})])
- self.assertEquals(path.apply(obj), 3)
+ path = Path(self.mock, None, [Action("getattr", ("x",), {}),
+ Action("getattr", ("y",), {}),
+ Action("call", (1,), {"b": 2})])
+ self.assertEquals(path.execute(obj), 3)
class MatchParamsTest(unittest.TestCase):
@@ -935,17 +1154,26 @@ class MatchParamsTest(unittest.TestCase):
self.assertEquals(repr(ANY), "ANY")
def test_any_equals(self):
- self.assertEquals(ANY, 1)
- self.assertEquals(ANY, 42)
self.assertEquals(ANY, ANY)
+ self.assertNotEquals(ANY, ARGS)
+ self.assertNotEquals(ANY, object())
+
+ def test_any_matches(self):
+ self.assertTrue(ANY.matches(1))
+ self.assertTrue(ANY.matches(42))
+ self.assertTrue(ANY.matches(object()))
def test_various_repr(self):
self.assertEquals(repr(VARIOUS), "VARIOUS")
def test_various_equals(self):
self.assertEquals(VARIOUS, VARIOUS)
- self.assertNotEquals(VARIOUS, ANY)
- self.assertNotEquals(ANY, VARIOUS)
+ self.assertNotEquals(VARIOUS, object())
+
+ def test_various_matches(self):
+ self.assertTrue(VARIOUS.matches(1))
+ self.assertTrue(VARIOUS.matches(42))
+ self.assertTrue(VARIOUS.matches(object()))
def test_same_repr(self):
self.assertEquals(repr(SAME("obj")), "SAME('obj')")
@@ -953,96 +1181,126 @@ class MatchParamsTest(unittest.TestCase):
def test_same_equals(self):
l1 = []
l2 = []
- self.assertEquals(SAME(l1), l1)
self.assertNotEquals(SAME(l1), l2)
-
self.assertEquals(SAME(l1), SAME(l1))
self.assertNotEquals(SAME(l1), SAME(l2))
- self.assertNotEquals(ANY, SAME(l1))
- self.assertNotEquals(SAME(l1), ANY)
+ def test_same_matches(self):
+ l1 = []
+ l2 = []
+ self.assertTrue(SAME(l1).matches(l1))
+ self.assertFalse(SAME(l1).matches(l2))
+ self.assertFalse(SAME(l1).matches(ANY))
def test_contains_repr(self):
self.assertEquals(repr(CONTAINS("obj")), "CONTAINS('obj')")
def test_contains_equals(self):
- self.assertEquals(CONTAINS(1), [1])
- self.assertNotEquals(CONTAINS([1]), [1])
-
self.assertEquals(CONTAINS([1]), CONTAINS([1]))
self.assertNotEquals(CONTAINS(1), CONTAINS([1]))
- self.assertNotEquals(ANY, CONTAINS(1))
- self.assertNotEquals(CONTAINS(1), ANY)
+ def test_contains_matches(self):
+ self.assertTrue(CONTAINS(1).matches([1]))
+ self.assertFalse(CONTAINS([1]).matches([1]))
+ self.assertFalse(CONTAINS(1).matches(object()))
+
+ def test_contains_matches_with_contains(self):
+ """Can't be iterated, but has contains hook."""
+ class C(object):
+ def __contains__(self, value):
+ return True
+ self.assertTrue(CONTAINS(1).matches(C()))
def test_normal(self):
- self.assertTrue(match_params((), {}, (), {}))
- self.assertTrue(match_params((1, 2), {"a": 3}, (1, 2), {"a": 3}))
- self.assertFalse(match_params((1,), {}, (), {}))
- self.assertFalse(match_params((), {}, (1,), {}))
- self.assertFalse(match_params((1, 2), {"a": 3}, (1, 2), {"a": 4}))
- self.assertFalse(match_params((1, 2), {"a": 3}, (1, 3), {"a": 3}))
+ self.true((), {}, (), {})
+ self.true((1, 2), {"a": 3}, (1, 2), {"a": 3})
+ self.false((1,), {}, (), {})
+ self.false((), {}, (1,), {})
+ self.false((1, 2), {"a": 3}, (1, 2), {"a": 4})
+ self.false((1, 2), {"a": 3}, (1, 3), {"a": 3})
def test_any(self):
- self.assertTrue(match_params((1, 2), {"a": ANY}, (1, 2), {"a": 4}))
- self.assertTrue(match_params((1, ANY), {"a": 3}, (1, 3), {"a": 3}))
- self.assertFalse(match_params((ANY,), {}, (), {}))
-
- def test_various_alone(self):
- self.true((VARIOUS,), {}, (), {})
- self.true((VARIOUS,), {}, (1, 2), {})
- self.true((VARIOUS,), {}, (1, 2), {"a": 2})
- self.true((VARIOUS,), {}, (), {"a": 2})
- self.true((VARIOUS,), {"a": 1}, (), {"a": 1})
- self.true((VARIOUS,), {"a": 1}, (1, 2), {"a": 1})
- self.true((VARIOUS,), {"a": 1}, (), {"a": 1, "b": 2})
- self.true((VARIOUS,), {"a": 1}, (1, 2), {"a": 1, "b": 2})
- self.false((VARIOUS,), {"a": 1}, (), {})
-
- def test_various_at_start(self):
- self.true((VARIOUS, 3, 4), {}, (3, 4), {})
- self.true((VARIOUS, 3, 4), {}, (1, 2, 3, 4), {})
- self.true((VARIOUS, 3, 4), {"a": 1}, (3, 4), {"a": 1})
- self.true((VARIOUS, 3, 4), {"a": 1}, (1, 2, 3, 4), {"a": 1, "b": 2})
- self.false((VARIOUS, 3, 4), {}, (), {})
- self.false((VARIOUS, 3, 4), {}, (3, 5), {})
- self.false((VARIOUS, 3, 4), {}, (5, 5), {})
- self.false((VARIOUS, 3, 4), {"a": 1}, (), {})
- self.false((VARIOUS, 3, 4), {"a": 1}, (3, 4), {})
- self.false((VARIOUS, 3, 4), {"a": 1}, (3, 4), {"b": 2})
-
- def test_various_at_end(self):
- self.true((1, 2, VARIOUS), {}, (1, 2), {})
- self.true((1, 2, VARIOUS), {}, (1, 2, 3, 4), {})
- self.true((1, 2, VARIOUS), {"a": 1}, (1, 2), {"a": 1})
- self.true((1, 2, VARIOUS), {"a": 1}, (1, 2, 3, 4), {"a": 1, "b": 2})
- self.false((1, 2, VARIOUS), {}, (), {})
- self.false((1, 2, VARIOUS), {}, (1, 3), {})
- self.false((1, 2, VARIOUS), {}, (3, 3), {})
- self.false((1, 2, VARIOUS), {"a": 1}, (), {})
- self.false((1, 2, VARIOUS), {"a": 1}, (1, 2), {})
- self.false((1, 2, VARIOUS), {"a": 1}, (1, 2), {"b": 2})
-
- def test_various_at_middle(self):
- self.true((1, VARIOUS, 4), {}, (1, 4), {})
- self.true((1, VARIOUS, 4), {}, (1, 2, 3, 4), {})
- self.true((1, VARIOUS, 4), {"a": 1}, (1, 4), {"a": 1})
- self.true((1, VARIOUS, 4), {"a": 1}, (1, 2, 3, 4), {"a": 1, "b": 2})
- self.false((1, VARIOUS, 4), {}, (), {})
- self.false((1, VARIOUS, 4), {}, (1, 5), {})
- self.false((1, VARIOUS, 4), {}, (5, 5), {})
- self.false((1, VARIOUS, 4), {"a": 1}, (), {})
- self.false((1, VARIOUS, 4), {"a": 1}, (1, 4), {})
- self.false((1, VARIOUS, 4), {"a": 1}, (1, 4), {"b": 2})
-
- def test_various_multiple(self):
- self.true((VARIOUS, 3, VARIOUS, 6, VARIOUS), {},
+ self.true((1, 2), {"a": ANY}, (1, 2), {"a": 4})
+ self.true((1, ANY), {"a": 3}, (1, 3), {"a": 3})
+ self.false((ANY,), {}, (), {})
+
+ def test_args_alone(self):
+ self.true((ARGS,), {}, (), {})
+ self.true((ARGS,), {}, (1, 2), {})
+ self.false((ARGS,), {}, (1, 2), {"a": 2})
+ self.false((ARGS,), {}, (), {"a": 2})
+ self.true((ARGS,), {"a": 1}, (), {"a": 1})
+ self.true((ARGS,), {"a": 1}, (1, 2), {"a": 1})
+ self.false((ARGS,), {"a": 1}, (), {"a": 1, "b": 2})
+ self.false((ARGS,), {"a": 1}, (1, 2), {"a": 1, "b": 2})
+ self.false((ARGS,), {"a": 1}, (), {})
+
+ def test_kwargs_alone(self):
+ self.true((KWARGS,), {}, (), {})
+ self.false((KWARGS,), {}, (1, 2), {})
+ self.false((KWARGS,), {}, (1, 2), {"a": 2})
+ self.true((KWARGS,), {}, (), {"a": 2})
+ self.true((KWARGS,), {"a": 1}, (), {"a": 1})
+ self.false((KWARGS,), {"a": 1}, (1, 2), {"a": 1})
+ self.true((KWARGS,), {"a": 1}, (), {"a": 1, "b": 2})
+ self.false((KWARGS,), {"a": 1}, (1, 2), {"a": 1, "b": 2})
+ self.false((KWARGS,), {"a": 1}, (), {})
+
+ def test_args_kwargs(self):
+ self.true((ARGS, KWARGS), {}, (), {})
+ self.true((ARGS, KWARGS), {}, (1, 2), {})
+ self.true((ARGS, KWARGS), {}, (1, 2), {"a": 2})
+ self.true((ARGS, KWARGS), {}, (), {"a": 2})
+ self.true((ARGS, KWARGS), {"a": 1}, (), {"a": 1})
+ self.true((ARGS, KWARGS), {"a": 1}, (1, 2), {"a": 1})
+ self.true((ARGS, KWARGS), {"a": 1}, (), {"a": 1, "b": 2})
+ self.true((ARGS, KWARGS), {"a": 1}, (1, 2), {"a": 1, "b": 2})
+ self.false((ARGS, KWARGS), {"a": 1}, (), {})
+
+ def test_args_at_start(self):
+ self.true((ARGS, 3, 4), {}, (3, 4), {})
+ self.true((ARGS, 3, 4), {}, (1, 2, 3, 4), {})
+ self.true((ARGS, 3, 4), {"a": 1}, (3, 4), {"a": 1})
+ self.false((ARGS, 3, 4), {"a": 1}, (1, 2, 3, 4), {"a": 1, "b": 2})
+ self.false((ARGS, 3, 4), {}, (), {})
+ self.false((ARGS, 3, 4), {}, (3, 5), {})
+ self.false((ARGS, 3, 4), {}, (5, 5), {})
+ self.false((ARGS, 3, 4), {"a": 1}, (), {})
+ self.false((ARGS, 3, 4), {"a": 1}, (3, 4), {})
+ self.false((ARGS, 3, 4), {"a": 1}, (3, 4), {"b": 2})
+
+ def test_args_at_end(self):
+ self.true((1, 2, ARGS), {}, (1, 2), {})
+ self.true((1, 2, ARGS), {}, (1, 2, 3, 4), {})
+ self.true((1, 2, ARGS), {"a": 1}, (1, 2), {"a": 1})
+ self.false((1, 2, ARGS), {"a": 1}, (1, 2, 3, 4), {"a": 1, "b": 2})
+ self.false((1, 2, ARGS), {}, (), {})
+ self.false((1, 2, ARGS), {}, (1, 3), {})
+ self.false((1, 2, ARGS), {}, (3, 3), {})
+ self.false((1, 2, ARGS), {"a": 1}, (), {})
+ self.false((1, 2, ARGS), {"a": 1}, (1, 2), {})
+ self.false((1, 2, ARGS), {"a": 1}, (1, 2), {"b": 2})
+
+ def test_args_at_middle(self):
+ self.true((1, ARGS, 4), {}, (1, 4), {})
+ self.true((1, ARGS, 4), {}, (1, 2, 3, 4), {})
+ self.true((1, ARGS, 4), {"a": 1}, (1, 4), {"a": 1})
+ self.false((1, ARGS, 4), {"a": 1}, (1, 2, 3, 4), {"a": 1, "b": 2})
+ self.false((1, ARGS, 4), {}, (), {})
+ self.false((1, ARGS, 4), {}, (1, 5), {})
+ self.false((1, ARGS, 4), {}, (5, 5), {})
+ self.false((1, ARGS, 4), {"a": 1}, (), {})
+ self.false((1, ARGS, 4), {"a": 1}, (1, 4), {})
+ self.false((1, ARGS, 4), {"a": 1}, (1, 4), {"b": 2})
+
+ def test_args_multiple(self):
+ self.true((ARGS, 3, ARGS, 6, ARGS), {},
(1, 2, 3, 4, 5, 6), {})
- self.true((VARIOUS, VARIOUS, VARIOUS), {}, (1, 2, 3, 4, 5, 6), {})
- self.true((VARIOUS, VARIOUS, VARIOUS), {}, (), {})
- self.false((VARIOUS, 3, VARIOUS, 6, VARIOUS), {},
+ self.true((ARGS, ARGS, ARGS), {}, (1, 2, 3, 4, 5, 6), {})
+ self.true((ARGS, ARGS, ARGS), {}, (), {})
+ self.false((ARGS, 3, ARGS, 6, ARGS), {},
(1, 2, 3, 4, 5), {})
- self.false((VARIOUS, 3, VARIOUS, 6, VARIOUS), {},
+ self.false((ARGS, 3, ARGS, 6, ARGS), {},
(1, 2, 4, 5, 6), {})
@@ -1055,95 +1313,150 @@ class MockTest(unittest.TestCase):
def act(path):
self.paths.append(path)
return 42
+ self.StubMocker = StubMocker
self.mocker = StubMocker()
- self.obj = Mock(self.mocker)
-
- def test_mocker(self):
- self.assertEquals(self.obj.__mocker__, self.mocker)
- self.assertEquals(self.obj.__mocker_name__, None)
-
- def test_default_path(self):
- path = self.obj.__mocker_path__
- self.assertEquals(path.root_mock, self.obj)
- self.assertEquals(path.actions, ())
+ self.mock = Mock(self.mocker)
+
+ def test_default_attributes(self):
+ self.assertEquals(self.mock.__mocker__, self.mocker)
+ self.assertEquals(self.mock.__mocker_path__, Path(self.mock))
+ self.assertEquals(self.mock.__mocker_name__, None)
+ self.assertEquals(self.mock.__mocker_spec__, None)
+ self.assertEquals(self.mock.__mocker_type__, None)
+ self.assertEquals(self.mock.__mocker_object__, None)
+ self.assertEquals(self.mock.__mocker_passthrough__, False)
+ self.assertEquals(self.mock.__mocker_patcher__, None)
+ self.assertEquals(self.mock.__mocker_replace__, False)
def test_path(self):
path = object()
self.assertEquals(Mock(self.mocker, path).__mocker_path__, path)
def test_object(self):
- obj = Mock(self.mocker, object="foo")
- self.assertEquals(obj.__mocker_object__, "foo")
+ mock = Mock(self.mocker, object="foo")
+ self.assertEquals(mock.__mocker_object__, "foo")
+ self.assertEquals(mock.__mocker_path__.root_object, "foo")
def test_passthrough(self):
- obj = Mock(self.mocker, object="foo", passthrough=True)
- self.assertEquals(obj.__mocker_object__, "foo")
- self.assertEquals(obj.__mocker_passthrough__, True)
+ mock = Mock(self.mocker, object="foo", passthrough=True)
+ self.assertEquals(mock.__mocker_object__, "foo")
+ self.assertEquals(mock.__mocker_passthrough__, True)
+
+ def test_spec(self):
+ C = object()
+ self.assertEquals(Mock(self.mocker, spec=C).__mocker_spec__, C)
+
+ def test_type(self):
+ def raise_exception(self, path):
+ raise MatchError
+ self.StubMocker.act = raise_exception
+ class C(object): pass
+ mock = Mock(self.mocker, type=C)
+ self.assertEquals(mock.__mocker_type__, C)
+ self.assertEquals(mock.__class__, C)
+ self.assertEquals(isinstance(mock, C), True)
def test_auto_naming(self):
- named_obj = self.obj
- named_obj.attr
- another_name = named_obj
- named_obj = None # Can't find this one anymore.
+ named_mock = self.mock
+ named_mock.attr
+ another_name = named_mock
+ named_mock = None # Can't find this one anymore.
another_name.attr
- self.assertEquals(another_name.__mocker_name__, "named_obj")
+ self.assertEquals(another_name.__mocker_name__, "named_mock")
def test_auto_naming_on_self(self):
- self.named_obj = self.obj
- del self.obj
- self.named_obj.attr
- self.assertEquals(self.named_obj.__mocker_name__, "named_obj")
+ self.named_mock = self.mock
+ del self.mock
+ self.named_mock.attr
+ self.assertEquals(self.named_mock.__mocker_name__, "named_mock")
def test_auto_naming_on_bad_self(self):
self_ = self
self = object() # No __dict__
- self_.named_obj = self_.obj
- self_.named_obj.attr
- self_.assertEquals(self_.named_obj.__mocker_name__, None)
+ self_.named_mock = self_.mock
+ self_.named_mock.attr
+ self_.assertEquals(self_.named_mock.__mocker_name__, None)
def test_auto_naming_without_getframe(self):
getframe = sys._getframe
sys._getframe = None
try:
- self.named_obj = self.obj
- self.named_obj.attr
- self.assertEquals(self.named_obj.__mocker_name__, None)
+ self.named_mock = self.mock
+ self.named_mock.attr
+ self.assertEquals(self.named_mock.__mocker_name__, None)
finally:
sys._getframe = getframe
def test_getattr(self):
- self.assertEquals(self.obj.attr, 42)
+ self.assertEquals(self.mock.attr, 42)
(path,) = self.paths
self.assertEquals(type(path), Path)
- self.assertTrue(path.parent_path is self.obj.__mocker_path__)
- self.assertEquals(path, self.obj.__mocker_path__ +
- Action(None, "getattr", ("attr",), {}))
+ self.assertTrue(path.parent_path is self.mock.__mocker_path__)
+ self.assertEquals(path, self.mock.__mocker_path__ +
+ Action("getattr", ("attr",), {}))
def test_call(self):
- self.obj(1, a=2)
+ self.mock(1, a=2)
(path,) = self.paths
self.assertEquals(type(path), Path)
- self.assertTrue(path.parent_path is self.obj.__mocker_path__)
- self.assertEquals(path, self.obj.__mocker_path__ +
- Action(None, "call", (1,), {"a": 2}))
+ self.assertTrue(path.parent_path is self.mock.__mocker_path__)
+ self.assertEquals(path, self.mock.__mocker_path__ +
+ Action("call", (1,), {"a": 2}))
def test_passthrough_on_unexpected(self):
class StubMocker(object):
def act(self, path):
if path.actions[-1].args == ("x",):
- raise UnexpectedExprError
+ raise MatchError
return 42
class C(object):
x = 123
y = 321
- obj = Mock(StubMocker(), object=C())
- self.assertRaises(UnexpectedExprError, getattr, obj, "x", 42)
- self.assertEquals(obj.y, 42)
+ mock = Mock(StubMocker(), object=C())
+ self.assertRaises(MatchError, getattr, mock, "x", 42)
+ self.assertEquals(mock.y, 42)
+
+ mock = Mock(StubMocker(), passthrough=True)
+ self.assertRaises(MatchError, getattr, mock, "x", 42)
+ self.assertEquals(mock.y, 42)
+
+ mock = Mock(StubMocker(), object=C(), passthrough=True)
+ self.assertEquals(mock.x, 123)
+ self.assertEquals(mock.y, 42)
+
+ mock = Mock(StubMocker(), passthrough=True)
+ act = mock.__mocker_act__
+ self.assertEquals(act("getattr", ("x",), 42, object=C()), 123)
+ self.assertEquals(act("getattr", ("y",), 42, object=C()), 42)
+
+ def test_act_with_object(self):
+ obj = object()
+ self.mock.__mocker_act__("kind", object=obj)
+ (path,) = self.paths
+ self.assertEquals(type(path), Path)
+ self.assertTrue(path.parent_path is self.mock.__mocker_path__)
+ self.assertTrue(path.root_object is obj)
+
+ def test_reraise_assertion(self):
+ class StubMocker(object):
+ def act(self, path):
+ message = os.linesep.join(["An", "- error", "- happened"])
+ raise AssertionError(message)
+ mock = Mock(StubMocker())
+ try:
+ mock.__mocker_act__("kind")
+ except AssertionError, e:
+ message = os.linesep.join(["[Mocker] Unmet expectation:",
+ "",
+ "=> An",
+ " - error",
+ " - happened",
+ ""])
+ self.assertEquals(str(e), message)
+ else:
+ self.fail("AssertionError not raised")
- obj = Mock(StubMocker(), object=C(), passthrough=True)
- self.assertEquals(obj.x, 123)
- self.assertEquals(obj.y, 42)
class EventTest(unittest.TestCase):
@@ -1220,9 +1533,32 @@ class EventTest(unittest.TestCase):
self.assertEquals(self.event.run(42), False)
self.assertEquals(calls, [42, 42, 42])
+ def test_run_errors(self):
+ class MyTask(object):
+ def __init__(self, id, failed):
+ self.id = id
+ self.failed = failed
+ def run(self, path):
+ if self.failed:
+ raise AssertionError("%d failed" % self.id)
+ event = Event("i.am.a.path")
+ event.add_task(MyTask(1, True))
+ event.add_task(MyTask(2, False))
+ event.add_task(MyTask(3, True))
+
+ try:
+ event.run(42)
+ except AssertionError, e:
+ message = os.linesep.join(["i.am.a.path",
+ "- 1 failed",
+ "- 3 failed"])
+ self.assertEquals(str(e), message)
+ else:
+ self.fail("AssertionError not raised")
+
def test_satisfied_false(self):
def raise_error():
- raise AssertionError
+ raise AssertionError("An error")
task1 = self.event.add_task(Task())
task2 = self.event.add_task(Task())
task2.verify = raise_error
@@ -1237,13 +1573,27 @@ class EventTest(unittest.TestCase):
self.assertEquals(self.event.satisfied(), True)
def test_verify(self):
- calls = []
- task1 = self.event.add_task(Task())
- task1.verify = lambda: calls.append(1)
- task2 = self.event.add_task(Task())
- task2.verify = lambda: calls.append(2)
- self.event.verify()
- self.assertEquals(calls, [1, 2])
+ class MyTask(object):
+ def __init__(self, id, failed):
+ self.id = id
+ self.failed = failed
+ def verify(self):
+ if self.failed:
+ raise AssertionError("%d failed" % self.id)
+ event = Event("i.am.a.path")
+ event.add_task(MyTask(1, True))
+ event.add_task(MyTask(2, False))
+ event.add_task(MyTask(3, True))
+
+ try:
+ event.verify()
+ except AssertionError, e:
+ message = os.linesep.join(["i.am.a.path",
+ "- 1 failed",
+ "- 3 failed"])
+ self.assertEquals(str(e), message)
+ else:
+ self.fail("AssertionError not raised")
def test_set_state(self):
calls = []
@@ -1277,7 +1627,7 @@ class PathMatcherTest(unittest.TestCase):
def setUp(self):
self.mocker = CleanMocker()
- self.mock = self.mocker.obj()
+ self.mock = self.mocker.mock()
def test_is_task(self):
self.assertTrue(isinstance(PathMatcher(None), Task))
@@ -1288,15 +1638,15 @@ class PathMatcherTest(unittest.TestCase):
self.assertEquals(task.path, path)
def test_matches(self):
- path = Path(self.mock, [Action(None, "getattr", ("attr1",), {})])
+ path = Path(self.mock, None, [Action("getattr", ("attr1",), {})])
task = PathMatcher(path)
- action = Action(Path(self.mock), "getattr", (), {})
+ action = Action("getattr", (), {}, Path(self.mock))
self.assertFalse(task.matches(action.path + action))
- action = Action(Path(self.mock), "getattr", ("attr1",), {})
+ action = Action("getattr", ("attr1",), {}, Path(self.mock))
self.assertTrue(task.matches(action.path + action))
def test_recorder(self):
- path = Path(self.mock, [Action(None, "call", (), {})])
+ path = Path(self.mock, [Action("call", (), {})])
event = Event(path)
path_matcher_recorder(self.mocker, event)
(task,) = event.get_tasks()
@@ -1311,8 +1661,8 @@ class RunCounterTest(unittest.TestCase):
def setUp(self):
self.mocker = CleanMocker()
- self.mock = self.mocker.obj()
- self.action = Action(Path(self.mock), "getattr", ("attr",), {})
+ self.mock = self.mocker.mock()
+ self.action = Action("getattr", ("attr",), {}, Path(self.mock))
self.path = Path(self.mock, [self.action])
self.event = Event(self.path)
@@ -1387,10 +1737,10 @@ class RunCounterTest(unittest.TestCase):
may be repeated any number of times.
"""
path1 = Path(self.mock)
- path2 = path1 + Action(path1, "getattr", ("attr",), {})
- path3 = path2 + Action(path2, "getattr", ("attr",), {})
- path4 = path3 + Action(path3, "call", (), {})
- path5 = path4 + Action(path4, "call", (), {})
+ path2 = path1 + Action("getattr", ("attr",), {}, path1)
+ path3 = path2 + Action("getattr", ("attr",), {}, path2)
+ path4 = path3 + Action("call", (), {}, path3)
+ path5 = path4 + Action("call", (), {}, path4)
event3 = self.mocker.add_event(Event(path3))
event2 = self.mocker.add_event(Event(path2))
@@ -1437,7 +1787,7 @@ class RunCounterTest(unittest.TestCase):
self.mocker.add_recorder(run_counter_recorder)
self.mocker.add_recorder(run_counter_removal_recorder)
- obj = self.mocker.obj()
+ obj = self.mocker.mock()
obj.x.y()()
@@ -1457,8 +1807,8 @@ class MockReturnerTest(unittest.TestCase):
def setUp(self):
self.mocker = CleanMocker()
- self.mock = self.mocker.obj()
- self.action = Action(Path(self.mock), "getattr", ("attr",), {})
+ self.mock = self.mocker.mock()
+ self.action = Action("getattr", ("attr",), {}, Path(self.mock))
self.path = Path(self.mock, [self.action])
self.event = Event(self.path)
@@ -1474,9 +1824,9 @@ class MockReturnerTest(unittest.TestCase):
def test_recorder(self):
path1 = Path(self.mock)
- path2 = path1 + Action(path1, "getattr", ("attr",), {})
- path3 = path2 + Action(path2, "getattr", ("attr",), {})
- path4 = path3 + Action(path3, "call", (), {})
+ path2 = path1 + Action("getattr", ("attr",), {}, path1)
+ path3 = path2 + Action("getattr", ("attr",), {}, path2)
+ path4 = path3 + Action("call", (), {}, path3)
event2 = self.mocker.add_event(Event(path2))
event3 = self.mocker.add_event(Event(path3))
@@ -1514,9 +1864,9 @@ class FunctionRunnerTest(unittest.TestCase):
def setUp(self):
self.mocker = CleanMocker()
- self.mock = self.mocker.obj()
- self.action = Action(Path(self.mock), "call", (1, 2), {"c": 3})
- self.path = Path(self.mock, [self.action])
+ self.mock = self.mocker.mock()
+ self.action = Action("call", (1, 2), {"c": 3}, Path(self.mock))
+ self.path = Path(self.mock, None, [self.action])
self.event = Event(self.path)
def test_is_task(self):
@@ -1528,13 +1878,13 @@ class FunctionRunnerTest(unittest.TestCase):
self.assertEquals(result, "((1, 2), {'c': 3})")
-class PathApplierTest(unittest.TestCase):
+class PathExecuterTest(unittest.TestCase):
def setUp(self):
self.mocker = CleanMocker()
def test_is_task(self):
- self.assertTrue(isinstance(PathApplier(), Task))
+ self.assertTrue(isinstance(PathExecuter(), Task))
def test_run(self):
class C(object):
@@ -1543,13 +1893,11 @@ class PathApplierTest(unittest.TestCase):
obj.x = C()
obj.x.y = lambda a, b: a+b
- proxy = self.mocker.proxy(obj)
+ path = Path(None, obj, [Action("getattr", ("x",), {}),
+ Action("getattr", ("y",), {}),
+ Action("call", (1,), {"b": 2})])
- path = Path(proxy, [Action(None, "getattr", ("x",), {}),
- Action(None, "getattr", ("y",), {}),
- Action(None, "call", (1,), {"b": 2})])
-
- task = PathApplier()
+ task = PathExecuter()
self.assertEquals(task.run(path), 3)
@@ -1557,8 +1905,8 @@ class OrdererTest(unittest.TestCase):
def setUp(self):
self.mocker = CleanMocker()
- self.mock = self.mocker.obj()
- self.action = Action(Path(self.mock), "call", (1, 2), {"c": 3})
+ self.mock = self.mocker.mock()
+ self.action = Action("call", (1, 2, Path(self.mock)), {"c": 3})
self.path = Path(self.mock, [self.action])
def test_is_task(self):
@@ -1605,10 +1953,10 @@ class SpecCheckerTest(unittest.TestCase):
def staticnoargs(): pass
self.cls = C
self.mocker = CleanMocker()
- self.mock = self.mocker.obj(self.cls)
+ self.mock = self.mocker.mock(self.cls)
def path(self, *args, **kwargs):
- action = Action(Path(self.mock), "call", args, kwargs)
+ action = Action("call", args, kwargs, Path(self.mock))
return action.path + action
def good(self, method_names, args_expr):
@@ -1647,9 +1995,19 @@ class SpecCheckerTest(unittest.TestCase):
def test_is_task(self):
self.assertTrue(isinstance(SpecChecker(self.cls.normal), Task))
+ def test_error_message(self):
+ task = SpecChecker(self.cls.normal)
+ try:
+ task.run(self.path(1))
+ except AssertionError, e:
+ self.assertEquals(str(e), "Specification is normal(a, b, c=3): "
+ "'b' not provided")
+ else:
+ self.fail("AssertionError not raised")
+
def test_recorder(self):
self.mocker.add_recorder(spec_checker_recorder)
- obj = self.mocker.obj(self.cls)
+ obj = self.mocker.mock(spec=self.cls)
obj.noargs()
getattr, call = self.mocker.get_events()
self.assertEquals(getattr.get_tasks(), [])
@@ -1659,7 +2017,7 @@ class SpecCheckerTest(unittest.TestCase):
def test_recorder_with_unexistent_method(self):
self.mocker.add_recorder(spec_checker_recorder)
- obj = self.mocker.obj(self.cls)
+ obj = self.mocker.mock(spec=self.cls)
obj.unexistent()
getattr, call = self.mocker.get_events()
self.assertEquals(getattr.get_tasks(), [])
@@ -1669,7 +2027,7 @@ class SpecCheckerTest(unittest.TestCase):
def test_recorder_second_action_isnt_call(self):
self.mocker.add_recorder(spec_checker_recorder)
- obj = self.mocker.obj(self.cls)
+ obj = self.mocker.mock(spec=self.cls)
obj.noargs.x
event1, event2 = self.mocker.get_events()
self.assertEquals(event1.get_tasks(), [])
@@ -1677,7 +2035,7 @@ class SpecCheckerTest(unittest.TestCase):
def test_recorder_first_action_isnt_getattr(self):
self.mocker.add_recorder(spec_checker_recorder)
- obj = self.mocker.obj(self.cls)
+ obj = self.mocker.mock(spec=self.cls)
obj("noargs").x
event1, event2 = self.mocker.get_events()
self.assertEquals(event1.get_tasks(), [])
@@ -1685,7 +2043,7 @@ class SpecCheckerTest(unittest.TestCase):
def test_recorder_more_than_two_actions(self):
self.mocker.add_recorder(spec_checker_recorder)
- obj = self.mocker.obj(self.cls)
+ obj = self.mocker.mock(spec=self.cls)
obj.noargs().x
event1, event2, event3 = self.mocker.get_events()
self.assertEquals(len(event1.get_tasks()), 0)
@@ -1742,23 +2100,23 @@ class SpecCheckerTest(unittest.TestCase):
self.bad("unexistent", "")
-class ProxyInstallerTest(unittest.TestCase):
+class ProxyReplacerTest(unittest.TestCase):
def setUp(self):
self.mocker = CleanMocker()
import calendar
self.mock = Mock(self.mocker, object=calendar)
- self.task = ProxyInstaller(self.mock)
+ self.task = ProxyReplacer(self.mock)
def tearDown(self):
self.task.set_state(RESTORE)
def test_is_task(self):
- self.assertTrue(isinstance(ProxyInstaller(None), Task))
+ self.assertTrue(isinstance(ProxyReplacer(None), Task))
def test_mock(self):
mock = object()
- task = ProxyInstaller(mock)
+ task = ProxyReplacer(mock)
self.assertEquals(task.mock, mock)
def test_matches_nothing(self):
@@ -1778,11 +2136,27 @@ class ProxyInstallerTest(unittest.TestCase):
self.task.set_state(REPLAY)
self.assertEquals(type(self.mock.__mocker_object__), ModuleType)
+ def test_install_protects_path(self):
+ self.task.set_state(REPLAY)
+ self.assertEquals(type(self.mock.__mocker_path__.root_object),
+ ModuleType)
+
def test_deinstall_protects_task(self):
self.task.set_state(REPLAY)
self.task.set_state(RESTORE)
self.assertEquals(type(self.task.mock), Mock)
+ def test_install_protects_anything_with_mocker_replace_false(self):
+ class C(object):
+ def __init__(self):
+ import calendar
+ self.calendar = calendar
+ self.__mocker_replace__ = False
+ obj = C()
+ self.task.set_state(REPLAY)
+ self.assertEquals(type(self.mock.__mocker_path__.root_object),
+ ModuleType)
+
def test_install_on_object(self):
class C(object):
def __init__(self):
@@ -1796,7 +2170,7 @@ class ProxyInstallerTest(unittest.TestCase):
def test_install_on_submodule(self):
from os import path
mock = Mock(self.mocker, object=path)
- task = ProxyInstaller(mock)
+ task = ProxyReplacer(mock)
task.set_state(REPLAY)
import os
self.assertEquals(type(os.path), Mock)
@@ -1830,12 +2204,315 @@ class ProxyInstallerTest(unittest.TestCase):
def test_deinstall_from_submodule(self):
from os import path
mock = Mock(self.mocker, object=path)
- task = ProxyInstaller(mock)
+ task = ProxyReplacer(mock)
task.set_state(REPLAY)
task.set_state(RESTORE)
import os
self.assertEquals(type(os.path), ModuleType)
+class PatcherTest(unittest.TestCase):
+
+ def setUp(self):
+ self.mocker = Mocker()
+ self.patcher = Patcher()
+ self.C = type("C", (object,), {})
+ self.D = type("D", (self.C,), {})
+ self.E = type("E", (), {})
+
+ class MockStub(object):
+ def __mocker_act__(self, kind, args=(), kwargs={}, object=None):
+ return (kind, args, kwargs, object)
+
+ self.MockStub = MockStub
+
+ def test_is_task(self):
+ self.assertTrue(isinstance(Patcher(), Task))
+
+ def test_matches_nothing(self):
+ self.assertFalse(self.patcher.matches(None))
+
+ def test_is_monitoring_unseen_class_kind(self):
+ self.assertFalse(self.patcher.is_monitoring(self.C, "kind"))
+
+ def test_monitor_class(self):
+ self.patcher.monitor(self.C, "kind")
+ self.assertTrue(self.patcher.is_monitoring(self.C, "kind"))
+
+ def test_monitor_subclass(self):
+ self.patcher.monitor(self.C, "kind")
+ self.assertTrue(self.patcher.is_monitoring(self.D, "kind"))
+
+ def test_monitor_unknown_class(self):
+ self.patcher.monitor(self.C, "kind")
+ self.assertFalse(self.patcher.is_monitoring(self.E, "kind"))
+
+ def test_is_monitoring_unseen_instance(self):
+ obj = self.E()
+ self.patcher.monitor(self.C, "kind")
+ self.assertFalse(self.patcher.is_monitoring(obj, "kind"))
+
+ def test_is_monitoring_instance_explicitly_monitored(self):
+ obj = self.C()
+ self.patcher.monitor(obj, "kind")
+ self.assertTrue(self.patcher.is_monitoring(obj, "kind"))
+
+ def test_is_monitoring_instance_monitored_by_class(self):
+ obj = self.D()
+ self.patcher.monitor(self.D, "kind")
+ self.assertTrue(self.patcher.is_monitoring(obj, "kind"))
+
+ def test_patch_attr(self):
+ self.patcher.patch_attr(self.C, "attr", "patch")
+ self.assertEquals(self.C.__dict__.get("attr"), "patch")
+
+ def test_patch_attr_and_restore(self):
+ self.patcher.patch_attr(self.C, "attr", "patch")
+ self.patcher.set_state(RESTORE)
+ self.assertTrue("attr" not in self.C.__dict__)
+
+ def test_patch_attr_and_restore_to_original(self):
+ self.C.attr = "original"
+ self.patcher.patch_attr(self.C, "attr", "patch")
+ self.patcher.set_state(RESTORE)
+ self.assertEquals(self.C.__dict__.get("attr"), "original")
+
+ def test_get_unpatched_attr_unpatched_undefined(self):
+ self.assertEquals(self.patcher.get_unpatched_attr(self.C, "attr"),
+ Undefined)
+
+ def test_get_unpatched_attr_unpatched(self):
+ self.C.attr = "original"
+ self.assertEquals(self.patcher.get_unpatched_attr(self.C, "attr"),
+ "original")
+
+ def test_get_unpatched_attr_defined_on_superclass(self):
+ self.C.attr = "original"
+ self.assertEquals(self.patcher.get_unpatched_attr(self.D, "attr"),
+ "original")
+
+ def test_get_unpatched_attr_defined_on_superclass_patched_on_sub(self):
+ self.C.attr = "original"
+ self.patcher.patch_attr(self.D, "attr", "patch")
+ self.assertEquals(self.patcher.get_unpatched_attr(self.D, "attr"),
+ "original")
+
+ def test_get_unpatched_attr_patched_originally_undefined(self):
+ self.patcher.patch_attr(self.C, "attr", "patch")
+ self.assertEquals(self.patcher.get_unpatched_attr(self.C, "attr"),
+ Undefined)
+
+ def test_get_unpatched_attr_patched(self):
+ self.C.attr = "original"
+ self.patcher.patch_attr(self.C, "attr", "patch")
+ self.assertEquals(self.patcher.get_unpatched_attr(self.C, "attr"),
+ "original")
+
+ def test_get_unpatched_attr_on_instance_originally_undefined(self):
+ self.assertEquals(self.patcher.get_unpatched_attr(self.C(), "attr"),
+ Undefined)
+
+ def test_get_unpatched_attr_on_instance(self):
+ self.C.attr = "original"
+ self.assertEquals(self.patcher.get_unpatched_attr(self.D(), "attr"),
+ "original")
+
+ def test_get_unpatched_attr_on_instance_defined_on_superclass(self):
+ self.C.attr = "original"
+ self.patcher.patch_attr(self.C, "attr", "patch")
+ self.assertEquals(self.patcher.get_unpatched_attr(self.D(), "attr"),
+ "original")
+
+ def test_get_unpatched_attr_on_instance_with_descriptor(self):
+ self.C.attr = property(lambda self: "original")
+ self.patcher.patch_attr(self.C, "attr", "patch")
+ self.assertEquals(self.patcher.get_unpatched_attr(self.D(), "attr"),
+ "original")
+
+ def test_get_unpatched_attr_on_instance_with_fake_descriptor(self):
+ class BadProperty(object):
+ def __init__(self):
+ # On real, __get__ must be on the class, not on the instance.
+ self.__get__ = lambda self, obj, cls=None: "original"
+ prop = BadProperty()
+ self.C.attr = prop
+ self.patcher.patch_attr(self.C, "attr", "patch")
+ self.assertEquals(self.patcher.get_unpatched_attr(self.D(), "attr"),
+ prop)
+
+ def test_replay_with_monitored_class(self):
+ self.patcher.monitor(self.C, "call")
+ self.patcher.set_state(REPLAY)
+ self.assertEquals(type(self.C.__dict__["__call__"]), PatchedMethod)
+
+ def test_replay_with_monitored_instance(self):
+ self.patcher.monitor(self.C(), "call")
+ self.patcher.set_state(REPLAY)
+ self.assertEquals(type(self.C.__dict__["__call__"]), PatchedMethod)
+
+ def test_replay_getattr(self):
+ self.patcher.monitor(self.C, "getattr")
+ self.patcher.set_state(REPLAY)
+ self.assertEquals(type(self.C.__dict__["__getattribute__"]),
+ PatchedMethod)
+
+ def test_restore(self):
+ self.patcher.monitor(self.C, "call")
+ self.patcher.set_state(REPLAY)
+ self.patcher.set_state(RESTORE)
+ self.assertTrue("__call__" not in self.C.__dict__)
+
+ def test_restore_twice_does_nothing(self):
+ self.patcher.monitor(self.C, "call")
+ self.patcher.set_state(REPLAY)
+ self.patcher.set_state(RESTORE)
+ self.C.__call__ = "original"
+ self.patcher.set_state(RESTORE)
+ self.assertTrue(self.C.__dict__.get("__call__"), "original")
+
+ def test_patched_call_on_instance(self):
+ self.patcher.monitor(self.C, "call")
+ obj = self.C()
+ obj.__mocker_mock__ = self.MockStub()
+ self.patcher.set_state(REPLAY)
+ result = obj(1, a=2)
+ self.assertEquals(result, ("call", (1,), {"a": 2}, obj))
+
+ def test_patched_call_on_class(self):
+ self.patcher.monitor(self.C, "call")
+ self.C.__mocker_mock__ = self.MockStub()
+ self.patcher.set_state(REPLAY)
+ obj = self.C()
+ result = obj(1, a=2)
+ self.assertEquals(result, ("call", (1,), {"a": 2}, obj))
+
+ def test_patched_call_on_class_edge_case(self):
+ """Only "getattr" kind should passthrough on __mocker_* arguments."""
+ self.patcher.monitor(self.C, "call")
+ self.C.__mocker_mock__ = self.MockStub()
+ self.patcher.set_state(REPLAY)
+ obj = self.C()
+ result = obj("__mocker_mock__")
+ self.assertEquals(result, ("call", ("__mocker_mock__",), {}, obj))
+
+ def test_patched_getattr_on_class(self):
+ self.patcher.monitor(self.C, "getattr")
+ self.C.__mocker_mock__ = self.MockStub()
+ self.patcher.set_state(REPLAY)
+ obj = self.C()
+ result = obj.attr
+ self.assertEquals(result, ("getattr", ("attr",), {}, obj))
+
+ def test_patched_getattr_on_unmonitored_object(self):
+ obj1 = self.C()
+ obj1.__mocker_mock__ = self.MockStub()
+ self.patcher.monitor(obj1, "getattr")
+ obj2 = self.C()
+ obj2.attr = "original"
+ self.patcher.set_state(REPLAY)
+ self.assertEquals(obj1.attr, ("getattr", ("attr",), {}, obj1))
+ self.assertEquals(obj2.attr, "original")
+
+ def test_patched_getattr_on_different_instances(self):
+ def build_getattr(original):
+ def __getattribute__(self, name):
+ if name == "attr":
+ return original
+ return object.__getattribute__(self, name)
+ return __getattribute__
+ self.C.__getattribute__ = build_getattr("originalC")
+ self.D.__getattribute__ = build_getattr("originalD")
+
+ class MockStub(object):
+ def __init__(self, id):
+ self.id = id
+ def __mocker_act__(self, kind, args=(), kwargs={}, object=None):
+ return self.id
+
+ obj1, obj2, obj3, obj4, obj5, obj6 = [self.C() for i in range(6)]
+ obj7, obj8, obj9 = [self.D() for i in range(3)]
+
+ obj2.__mocker_mock__ = MockStub(2)
+ self.patcher.monitor(obj2, "getattr")
+ obj5.__mocker_mock__ = MockStub(5)
+ self.patcher.monitor(obj5, "getattr")
+ obj8.__mocker_mock__ = MockStub(8)
+ self.patcher.monitor(obj8, "getattr")
+
+ self.patcher.set_state(REPLAY)
+ self.assertEquals(obj1.attr, "originalC")
+ self.assertEquals(obj2.attr, 2)
+ self.assertEquals(obj3.attr, "originalC")
+ self.assertEquals(obj4.attr, "originalC")
+ self.assertEquals(obj5.attr, 5)
+ self.assertEquals(obj6.attr, "originalC")
+ self.assertEquals(obj7.attr, "originalD")
+ self.assertEquals(obj8.attr, 8)
+ self.assertEquals(obj9.attr, "originalD")
+
+ def test_execute_getattr(self):
+ class C(object):
+ def __getattribute__(self, attr):
+ if attr == "attr":
+ return "original"
+ action = Action("getattr", ("attr",), {})
+ obj = C()
+ self.patcher.monitor(obj, "getattr")
+ self.patcher.set_state(REPLAY)
+ self.assertEquals(self.patcher.execute(action, obj), "original")
+
+ def test_execute_getattr_on_unexistent(self):
+ action = Action("getattr", ("attr",), {})
+ obj = self.C()
+ self.patcher.monitor(obj, "getattr")
+ self.patcher.set_state(REPLAY)
+ self.assertRaises(AttributeError, self.patcher.execute, action, obj)
+
+ def test_execute_call(self):
+ class C(object):
+ def __call__(self, *args, **kwargs):
+ return (args, kwargs)
+ action = Action("call", (1,), {"a": 2})
+ obj = C()
+ self.patcher.monitor(obj, "call")
+ self.patcher.set_state(REPLAY)
+ self.assertEquals(self.patcher.execute(action, obj), ((1,), {"a": 2}))
+
+ def test_recorder_class_getattr(self):
+ mock = self.mocker.patch(self.C)
+ mock.method()
+ self.mocker.result("mocked")
+ self.mocker.replay()
+ self.assertEquals(self.C().method(), "mocked")
+ self.assertRaises(AssertionError, self.C().method)
+
+ def test_recorder_instance_getattr(self):
+ self.C.attr = "original"
+ obj1 = self.C()
+ obj2 = self.C()
+ mock = self.mocker.patch(obj1)
+ mock.attr
+ self.mocker.result("mocked")
+ self.mocker.replay()
+ self.assertEquals(obj1.attr, "mocked")
+ self.assertRaises(AssertionError, getattr, obj1, "attr")
+ self.assertEquals(obj2.attr, "original")
+ self.assertRaises(AttributeError, getattr, obj1, "unexistent")
+
+ def test_recorder_passthrough(self):
+ class C(object):
+ def __init__(self):
+ self.result = "original" # Value on object's dictionary.
+ def method(self):
+ return self.result
+ mock = self.mocker.patch(C)
+ mock.method()
+ self.mocker.passthrough()
+ self.mocker.replay()
+ obj = C()
+ self.assertEquals(obj.method(), "original")
+ self.assertRaises(AssertionError, obj.method)
+
+
if __name__ == "__main__":
unittest.main()