summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPrzemyslaw Gajda <quermit@gmail.com>2012-04-22 17:45:03 +0200
committerPrzemyslaw Gajda <quermit@gmail.com>2012-04-22 17:45:03 +0200
commit3becdfd4a0be62be9d09a744fe74c7636f4f5754 (patch)
tree6f61e060cbd67250ebe11b1ae12d5ac10ad7489f
parente6127210cc59271fd2ed02c6a6ceb11b7efd63af (diff)
downloadpymox-3becdfd4a0be62be9d09a744fe74c7636f4f5754.tar.gz
Updated to version 0.5.3
-rwxr-xr-xmox.py505
-rwxr-xr-xmox_test.py656
-rwxr-xr-xmox_test_helper.py66
-rwxr-xr-xsetup.py17
-rw-r--r--stubout_testee.py14
5 files changed, 1197 insertions, 61 deletions
diff --git a/mox.py b/mox.py
index 8bb2099..2ffdf90 100755
--- a/mox.py
+++ b/mox.py
@@ -130,7 +130,7 @@ class UnexpectedMethodCallError(Error):
diff = differ.compare(str(unexpected_method).splitlines(True),
str(expected).splitlines(True))
self._str = ("Unexpected method call. unexpected:- expected:+\n%s"
- % ("\n".join(diff),))
+ % ("\n".join(line.rstrip() for line in diff),))
def __str__(self):
return self._str
@@ -156,13 +156,86 @@ class UnknownMethodCallError(Error):
self._unknown_method_name
+class PrivateAttributeError(Error):
+ """
+ Raised if a MockObject is passed a private additional attribute name.
+ """
+
+ def __init__(self, attr):
+ Error.__init__(self)
+ self._attr = attr
+
+ def __str__(self):
+ return ("Attribute '%s' is private and should not be available in a mock "
+ "object." % attr)
+
+
+class ExpectedMockCreationError(Error):
+ """Raised if mocks should have been created by StubOutClassWithMocks."""
+
+ def __init__(self, expected_mocks):
+ """Init exception.
+
+ Args:
+ # expected_mocks: A sequence of MockObjects that should have been
+ # created
+
+ Raises:
+ ValueError: if expected_mocks contains no methods.
+ """
+
+ if not expected_mocks:
+ raise ValueError("There must be at least one expected method")
+ Error.__init__(self)
+ self._expected_mocks = expected_mocks
+
+ def __str__(self):
+ mocks = "\n".join(["%3d. %s" % (i, m)
+ for i, m in enumerate(self._expected_mocks)])
+ return "Verify: Expected mocks never created:\n%s" % (mocks,)
+
+
+class UnexpectedMockCreationError(Error):
+ """Raised if too many mocks were created by StubOutClassWithMocks."""
+
+ def __init__(self, instance, *params, **named_params):
+ """Init exception.
+
+ Args:
+ # instance: the type of obejct that was created
+ # params: parameters given during instantiation
+ # named_params: named parameters given during instantiation
+ """
+
+ Error.__init__(self)
+ self._instance = instance
+ self._params = params
+ self._named_params = named_params
+
+ def __str__(self):
+ args = ", ".join(["%s" % v for i, v in enumerate(self._params)])
+ error = "Unexpected mock creation: %s(%s" % (self._instance, args)
+
+ if self._named_params:
+ error += ", " + ", ".join(["%s=%s" % (k, v) for k, v in
+ self._named_params.iteritems()])
+
+ error += ")"
+ return error
+
+
class Mox(object):
"""Mox: a factory for creating mock objects."""
# A list of types that should be stubbed out with MockObjects (as
# opposed to MockAnythings).
- _USE_MOCK_OBJECT = [types.ClassType, types.InstanceType, types.ModuleType,
- types.ObjectType, types.TypeType]
+ _USE_MOCK_OBJECT = [types.ClassType, types.FunctionType, types.InstanceType,
+ types.ModuleType, types.ObjectType, types.TypeType,
+ types.MethodType, types.UnboundMethodType,
+ ]
+
+ # A list of types that may be stubbed out with a MockObjectFactory.
+ _USE_MOCK_FACTORY = [types.ClassType, types.ObjectType, types.TypeType]
def __init__(self):
"""Initialize a new Mox."""
@@ -170,18 +243,21 @@ class Mox(object):
self._mock_objects = []
self.stubs = stubout.StubOutForTesting()
- def CreateMock(self, class_to_mock):
+ def CreateMock(self, class_to_mock, attrs=None):
"""Create a new mock object.
Args:
# class_to_mock: the class to be mocked
class_to_mock: class
+ attrs: dict of attribute names to values that will be set on the mock
+ object. Only public attributes may be set.
Returns:
MockObject that can be used as the class_to_mock would be.
"""
-
- new_mock = MockObject(class_to_mock)
+ if attrs is None:
+ attrs = {}
+ new_mock = MockObject(class_to_mock, attrs=attrs)
self._mock_objects.append(new_mock)
return new_mock
@@ -232,19 +308,72 @@ class Mox(object):
"""
attr_to_replace = getattr(obj, attr_name)
+ attr_type = type(attr_to_replace)
- # Check for a MockAnything. This could cause confusing problems later on.
- if attr_to_replace == MockAnything():
+ if attr_type == MockAnything or attr_type == MockObject:
raise TypeError('Cannot mock a MockAnything! Did you remember to '
'call UnsetStubs in your previous test?')
- if type(attr_to_replace) in self._USE_MOCK_OBJECT and not use_mock_anything:
+ if attr_type in self._USE_MOCK_OBJECT and not use_mock_anything:
stub = self.CreateMock(attr_to_replace)
else:
stub = self.CreateMockAnything(description='Stub for %s' % attr_to_replace)
+ stub.__name__ = attr_name
self.stubs.Set(obj, attr_name, stub)
+ def StubOutClassWithMocks(self, obj, attr_name):
+ """Replace a class with a "mock factory" that will create mock objects.
+
+ This is useful if the code-under-test directly instantiates
+ dependencies. Previously some boilder plate was necessary to
+ create a mock that would act as a factory. Using
+ StubOutClassWithMocks, once you've stubbed out the class you may
+ use the stubbed class as you would any other mock created by mox:
+ during the record phase, new mock instances will be created, and
+ during replay, the recorded mocks will be returned.
+
+ In replay mode
+
+ # Example using StubOutWithMock (the old, clunky way):
+
+ mock1 = mox.CreateMock(my_import.FooClass)
+ mock2 = mox.CreateMock(my_import.FooClass)
+ foo_factory = mox.StubOutWithMock(my_import, 'FooClass',
+ use_mock_anything=True)
+ foo_factory(1, 2).AndReturn(mock1)
+ foo_factory(9, 10).AndReturn(mock2)
+ mox.ReplayAll()
+
+ my_import.FooClass(1, 2) # Returns mock1 again.
+ my_import.FooClass(9, 10) # Returns mock2 again.
+ mox.VerifyAll()
+
+ # Example using StubOutClassWithMocks:
+
+ mox.StubOutClassWithMocks(my_import, 'FooClass')
+ mock1 = my_import.FooClass(1, 2) # Returns a new mock of FooClass
+ mock2 = my_import.FooClass(9, 10) # Returns another mock instance
+ mox.ReplayAll()
+
+ my_import.FooClass(1, 2) # Returns mock1 again.
+ my_import.FooClass(9, 10) # Returns mock2 again.
+ mox.VerifyAll()
+ """
+ attr_to_replace = getattr(obj, attr_name)
+ attr_type = type(attr_to_replace)
+
+ if attr_type == MockAnything or attr_type == MockObject:
+ raise TypeError('Cannot mock a MockAnything! Did you remember to '
+ 'call UnsetStubs in your previous test?')
+
+ if attr_type not in self._USE_MOCK_FACTORY:
+ raise TypeError('Given attr is not a Class. Use StubOutWithMock.')
+
+ factory = _MockObjectFactory(attr_to_replace, self)
+ self._mock_objects.append(factory)
+ self.stubs.Set(obj, attr_name, factory)
+
def UnsetStubs(self):
"""Restore stubs to their original state."""
@@ -299,11 +428,11 @@ class MockAnything:
self._description = description
self._Reset()
- def __str__(self):
- return "<MockAnything instance at %s>" % id(self)
-
def __repr__(self):
- return '<MockAnything instance>'
+ if self._description:
+ return '<MockAnything instance of %s>' % self._description
+ else:
+ return '<MockAnything instance>'
def __getattr__(self, method_name):
"""Intercept method calls on this object.
@@ -319,6 +448,8 @@ class MockAnything:
Returns:
A new MockMethod aware of MockAnything's state (record or replay).
"""
+ if method_name == '__dir__':
+ return self.__class__.__dir__.__get__(self, self.__class__)
return self._CreateMockMethod(method_name)
@@ -392,7 +523,7 @@ class MockAnything:
class MockObject(MockAnything, object):
"""A mock object that simulates the public/protected interface of a class."""
- def __init__(self, class_to_mock):
+ def __init__(self, class_to_mock, attrs=None):
"""Initialize a mock object.
This determines the methods and properties of the class and stores them.
@@ -400,7 +531,15 @@ class MockObject(MockAnything, object):
Args:
# class_to_mock: class to be mocked
class_to_mock: class
+ attrs: dict of attribute names to values that will be set on the mock
+ object. Only public attributes may be set.
+
+ Raises:
+ PrivateAttributeError: if a supplied attribute is not public.
+ ValueError: if an attribute would mask an existing method.
"""
+ if attrs is None:
+ attrs = {}
# This is used to hack around the mixin/inheritance of MockAnything, which
# is not a proper object (it can be anything. :-)
@@ -410,12 +549,34 @@ class MockObject(MockAnything, object):
self._known_methods = set()
self._known_vars = set()
self._class_to_mock = class_to_mock
+ try:
+ if inspect.isclass(self._class_to_mock):
+ self._description = class_to_mock.__name__
+ else:
+ self._description = type(class_to_mock).__name__
+ except Exception:
+ pass
+
for method in dir(class_to_mock):
- if callable(getattr(class_to_mock, method)):
+ attr = getattr(class_to_mock, method)
+ if callable(attr):
self._known_methods.add(method)
- else:
+ elif not (type(attr) is property):
+ # treating properties as class vars makes little sense.
self._known_vars.add(method)
+ # Set additional attributes at instantiation time; this is quicker
+ # than manually setting attributes that are normally created in
+ # __init__.
+ for attr, value in attrs.items():
+ if attr.startswith("_"):
+ raise PrivateAttributeError(attr)
+ elif attr in self._known_methods:
+ raise ValueError("'%s' is a method of '%s' objects." % (attr,
+ class_to_mock))
+ else:
+ setattr(self, attr, value)
+
def __getattr__(self, name):
"""Intercept attribute request on this object.
@@ -596,7 +757,16 @@ class MockObject(MockAnything, object):
# Because the call is happening directly on this object instead of a method,
# the call on the mock method is made right here
- mock_method = self._CreateMockMethod('__call__')
+
+ # If we are mocking a Function, then use the function, and not the
+ # __call__ method
+ method = None
+ if type(self._class_to_mock) in (types.FunctionType, types.MethodType):
+ method = self._class_to_mock;
+ else:
+ method = getattr(self._class_to_mock, '__call__')
+ mock_method = self._CreateMockMethod('__call__', method_to_mock=method)
+
return mock_method(*params, **named_params)
@property
@@ -605,8 +775,61 @@ class MockObject(MockAnything, object):
return self._class_to_mock
+ @property
+ def __name__(self):
+ """Return the name that is being mocked."""
+ return self._description
+
+
+class _MockObjectFactory(MockObject):
+ """A MockObjectFactory creates mocks and verifies __init__ params.
+
+ A MockObjectFactory removes the boiler plate code that was previously
+ necessary to stub out direction instantiation of a class.
+
+ The MockObjectFactory creates new MockObjects when called and verifies the
+ __init__ params are correct when in record mode. When replaying, existing
+ mocks are returned, and the __init__ params are verified.
+
+ See StubOutWithMock vs StubOutClassWithMocks for more detail.
+ """
+
+ def __init__(self, class_to_mock, mox_instance):
+ MockObject.__init__(self, class_to_mock)
+ self._mox = mox_instance
+ self._instance_queue = deque()
+
+ def __call__(self, *params, **named_params):
+ """Instantiate and record that a new mock has been created."""
+
+ method = getattr(self._class_to_mock, '__init__')
+ mock_method = self._CreateMockMethod('__init__', method_to_mock=method)
+ # Note: calling mock_method() is deferred in order to catch the
+ # empty instance_queue first.
+
+ if self._replay_mode:
+ if not self._instance_queue:
+ raise UnexpectedMockCreationError(self._class_to_mock, *params,
+ **named_params)
+
+ mock_method(*params, **named_params)
+
+ return self._instance_queue.pop()
+ else:
+ mock_method(*params, **named_params)
+
+ instance = self._mox.CreateMock(self._class_to_mock)
+ self._instance_queue.appendleft(instance)
+ return instance
+
+ def _Verify(self):
+ """Verify that all mocks have been created."""
+ if self._instance_queue:
+ raise ExpectedMockCreationError(self._instance_queue)
+ super(_MockObjectFactory, self)._Verify()
+
-class MethodCallChecker(object):
+class MethodSignatureChecker(object):
"""Ensures that methods are called correctly."""
_NEEDED, _DEFAULT, _GIVEN = range(3)
@@ -630,6 +853,7 @@ class MethodCallChecker(object):
if inspect.ismethod(method):
self._args = self._args[1:] # Skip 'self'.
self._method = method
+ self._instance = None # May contain the instance this is bound to.
self._has_varargs = varargs is not None
self._has_varkw = varkw is not None
@@ -652,9 +876,9 @@ class MethodCallChecker(object):
Raises:
AttributeError: arg_name is already marked as _GIVEN.
"""
- if arg_status.get(arg_name, None) == MethodCallChecker._GIVEN:
+ if arg_status.get(arg_name, None) == MethodSignatureChecker._GIVEN:
raise AttributeError('%s provided more than once' % (arg_name,))
- arg_status[arg_name] = MethodCallChecker._GIVEN
+ arg_status[arg_name] = MethodSignatureChecker._GIVEN
def Check(self, params, named_params):
"""Ensures that the parameters used while recording a call are valid.
@@ -668,10 +892,45 @@ class MethodCallChecker(object):
Raises:
AttributeError: the given parameters don't work with the given method.
"""
- arg_status = dict((a, MethodCallChecker._NEEDED)
+ arg_status = dict((a, MethodSignatureChecker._NEEDED)
for a in self._required_args)
for arg in self._default_args:
- arg_status[arg] = MethodCallChecker._DEFAULT
+ arg_status[arg] = MethodSignatureChecker._DEFAULT
+
+ # WARNING: Suspect hack ahead.
+ #
+ # Check to see if this is an unbound method, where the instance
+ # should be bound as the first argument. We try to determine if
+ # the first argument (param[0]) is an instance of the class, or it
+ # is equivalent to the class (used to account for Comparators).
+ #
+ # NOTE: If a Func() comparator is used, and the signature is not
+ # correct, this will cause extra executions of the function.
+ if inspect.ismethod(self._method):
+ # The extra param accounts for the bound instance.
+ if len(params) > len(self._required_args):
+ expected = getattr(self._method, 'im_class', None)
+
+ # Check if the param is an instance of the expected class,
+ # or check equality (useful for checking Comparators).
+
+ # This is a hack to work around the fact that the first
+ # parameter can be a Comparator, and the comparison may raise
+ # an exception during this comparison, which is OK.
+ try:
+ param_equality = (params[0] == expected)
+ except:
+ param_equality = False;
+
+
+ if isinstance(params[0], expected) or param_equality:
+ params = params[1:]
+ # If the IsA() comparator is being used, we need to check the
+ # inverse of the usual case - that the given instance is a subclass
+ # of the expected class. For example, the code under test does
+ # late binding to a subclass.
+ elif isinstance(params[0], IsA) and params[0]._IsSubClass(expected):
+ params = params[1:]
# Check that each positional param is valid.
for i in range(len(params)):
@@ -693,9 +952,9 @@ class MethodCallChecker(object):
# Ensure all the required arguments have been given.
still_needed = [k for k, v in arg_status.iteritems()
- if v == MethodCallChecker._NEEDED]
+ if v == MethodSignatureChecker._NEEDED]
if still_needed:
- raise AttributeError('No values given for arguments %s'
+ raise AttributeError('No values given for arguments: %s'
% (' '.join(sorted(still_needed))))
@@ -729,6 +988,7 @@ class MockMethod(object):
"""
self._name = method_name
+ self.__name__ = method_name
self._call_queue = call_queue
if not isinstance(call_queue, deque):
self._call_queue = deque(self._call_queue)
@@ -742,7 +1002,7 @@ class MockMethod(object):
self._side_effects = None
try:
- self._checker = MethodCallChecker(method_to_mock)
+ self._checker = MethodSignatureChecker(method_to_mock)
except ValueError:
self._checker = None
@@ -771,7 +1031,9 @@ class MockMethod(object):
expected_method = self._VerifyMethodCall()
if expected_method._side_effects:
- expected_method._side_effects(*params, **named_params)
+ result = expected_method._side_effects(*params, **named_params)
+ if expected_method._return_value is None:
+ expected_method._return_value = result
if expected_method._exception:
raise expected_method._exception
@@ -923,7 +1185,7 @@ class MockMethod(object):
"""Move this method into group of calls which may be called multiple times.
A group of repeating calls must be defined together, and must be executed in
- full before the next expected mehtod can be called.
+ full before the next expected method can be called.
Args:
group_name: the name of the unordered group.
@@ -1004,6 +1266,17 @@ class Comparator:
def __ne__(self, rhs):
return not self.equals(rhs)
+class Is(Comparator):
+ """Comparison class used to check identity, instead of equality."""
+
+ def __init__(self, obj):
+ self._obj = obj
+
+ def equals(self, rhs):
+ return rhs is self._obj
+
+ def __repr__(self):
+ return "<is %r (%s)>" % (self._obj, id(self._obj))
class IsA(Comparator):
"""This class wraps a basic Python type or class. It is used to verify
@@ -1040,8 +1313,26 @@ class IsA(Comparator):
# things like cStringIO.StringIO.
return type(rhs) == type(self._class_name)
+ def _IsSubClass(self, clazz):
+ """Check to see if the IsA comparators class is a subclass of clazz.
+
+ Args:
+ # clazz: a class object
+
+ Returns:
+ bool
+ """
+
+ try:
+ return issubclass(self._class_name, clazz)
+ except TypeError:
+ # Check raw types if there was a type error. This is helpful for
+ # things like cStringIO.StringIO.
+ return type(clazz) == type(self._class_name)
+
def __repr__(self):
- return str(self._class_name)
+ return 'mox.IsA(%s) ' % str(self._class_name)
+
class IsAlmost(Comparator):
"""Comparison class used to check whether a parameter is nearly equal
@@ -1073,7 +1364,7 @@ class IsAlmost(Comparator):
try:
return round(rhs-self._float_value, self._places) == 0
- except TypeError:
+ except Exception:
# This is probably because either float_value or rhs is not a number.
return False
@@ -1144,7 +1435,10 @@ class Regex(Comparator):
bool
"""
- return self.regex.search(rhs) is not None
+ try:
+ return self.regex.search(rhs) is not None
+ except Exception:
+ return False
def __repr__(self):
s = '<regular expression \'%s\'' % self.regex.pattern
@@ -1180,10 +1474,13 @@ class In(Comparator):
bool
"""
- return self._key in rhs
+ try:
+ return self._key in rhs
+ except Exception:
+ return False
def __repr__(self):
- return '<sequence or map containing \'%s\'>' % self._key
+ return '<sequence or map containing \'%s\'>' % str(self._key)
class Not(Comparator):
@@ -1214,7 +1511,10 @@ class Not(Comparator):
bool
"""
- return not self._predicate.equals(rhs)
+ try:
+ return not self._predicate.equals(rhs)
+ except Exception:
+ return False
def __repr__(self):
return '<not \'%s\'>' % self._predicate
@@ -1251,11 +1551,43 @@ class ContainsKeyValue(Comparator):
return False
def __repr__(self):
- return '<map containing the entry \'%s: %s\'>' % (self._key, self._value)
+ return '<map containing the entry \'%s: %s\'>' % (str(self._key),
+ str(self._value))
+
+
+class ContainsAttributeValue(Comparator):
+ """Checks whether a passed parameter contains attributes with a given value.
+
+ Example:
+ mock_dao.UpdateSomething(ContainsAttribute('stevepm', stevepm_user_info))
+ """
+
+ def __init__(self, key, value):
+ """Initialize.
+
+ Args:
+ # key: an attribute name of an object
+ # value: the corresponding value
+ """
+
+ self._key = key
+ self._value = value
+
+ def equals(self, rhs):
+ """Check whether the given attribute has a matching value in the rhs object.
+
+ Returns:
+ bool
+ """
+
+ try:
+ return getattr(rhs, self._key) == self._value
+ except Exception:
+ return False
class SameElementsAs(Comparator):
- """Checks whether iterables contain the same elements (ignoring order).
+ """Checks whether sequences contain the same elements (ignoring order).
Example:
mock_dao.ProcessUsers(SameElementsAs('stevepm', 'salomaki'))
@@ -1267,8 +1599,8 @@ class SameElementsAs(Comparator):
Args:
expected_seq: a sequence
"""
-
- self._expected_seq = expected_seq
+ # Store in case expected_seq is an iterator.
+ self._expected_list = list(expected_seq)
def equals(self, actual_seq):
"""Check to see whether actual_seq has same elements as expected_seq.
@@ -1279,20 +1611,30 @@ class SameElementsAs(Comparator):
Returns:
bool
"""
+ try:
+ # Store in case actual_seq is an iterator. We potentially iterate twice:
+ # once to make the dict, once in the list fallback.
+ actual_list = list(actual_seq)
+ except TypeError:
+ # actual_seq cannot be read as a sequence.
+ #
+ # This happens because Mox uses __eq__ both to check object equality (in
+ # MethodSignatureChecker) and to invoke Comparators.
+ return False
try:
- expected = dict([(element, None) for element in self._expected_seq])
- actual = dict([(element, None) for element in actual_seq])
+ expected = dict([(element, None) for element in self._expected_list])
+ actual = dict([(element, None) for element in actual_list])
except TypeError:
# Fall back to slower list-compare if any of the objects are unhashable.
- expected = list(self._expected_seq)
- actual = list(actual_seq)
+ expected = self._expected_list
+ actual = actual_list
expected.sort()
actual.sort()
return expected == actual
def __repr__(self):
- return '<sequence with same elements as \'%s\'>' % self._expected_seq
+ return '<sequence with same elements as \'%s\'>' % self._expected_list
class And(Comparator):
@@ -1431,6 +1773,61 @@ class IgnoreArg(Comparator):
return '<IgnoreArg>'
+class Value(Comparator):
+ """Compares argument against a remembered value.
+
+ To be used in conjunction with Remember comparator. See Remember()
+ for example.
+ """
+
+ def __init__(self):
+ self._value = None
+ self._has_value = False
+
+ def store_value(self, rhs):
+ self._value = rhs
+ self._has_value = True
+
+ def equals(self, rhs):
+ if not self._has_value:
+ return False
+ else:
+ return rhs == self._value
+
+ def __repr__(self):
+ if self._has_value:
+ return "<Value %r>" % self._value
+ else:
+ return "<Value>"
+
+
+class Remember(Comparator):
+ """Remembers the argument to a value store.
+
+ To be used in conjunction with Value comparator.
+
+ Example:
+ # Remember the argument for one method call.
+ users_list = Value()
+ mock_dao.ProcessUsers(Remember(users_list))
+
+ # Check argument against remembered value.
+ mock_dao.ReportUsers(users_list)
+ """
+
+ def __init__(self, value_store):
+ if not isinstance(value_store, Value):
+ raise TypeError("value_store is not an instance of the Value class")
+ self._value_store = value_store
+
+ def equals(self, rhs):
+ self._value_store.store_value(rhs)
+ return True
+
+ def __repr__(self):
+ return "<Remember %d>" % id(self._value_store)
+
+
class MethodGroup(object):
"""Base class containing common behaviour for MethodGroups."""
@@ -1463,6 +1860,12 @@ class UnorderedGroup(MethodGroup):
super(UnorderedGroup, self).__init__(group_name)
self._methods = []
+ def __str__(self):
+ return '%s "%s" pending calls:\n%s' % (
+ self.__class__.__name__,
+ self._group_name,
+ "\n".join(str(method) for method in self._methods))
+
def AddMethod(self, mock_method):
"""Add a method to this group.
@@ -1589,7 +1992,8 @@ class MoxMetaTestBase(type):
# for a case when test class is not the immediate child of MoxTestBase
for base in bases:
for attr_name in dir(base):
- d[attr_name] = getattr(base, attr_name)
+ if attr_name not in d:
+ d[attr_name] = getattr(base, attr_name)
for func_name, func in d.items():
if func_name.startswith('test') and callable(func):
@@ -1611,14 +2015,21 @@ class MoxMetaTestBase(type):
"""
def new_method(self, *args, **kwargs):
mox_obj = getattr(self, 'mox', None)
+ stubout_obj = getattr(self, 'stubs', None)
cleanup_mox = False
+ cleanup_stubout = False
if mox_obj and isinstance(mox_obj, Mox):
cleanup_mox = True
+ if stubout_obj and isinstance(stubout_obj, stubout.StubOutForTesting):
+ cleanup_stubout = True
try:
func(self, *args, **kwargs)
finally:
if cleanup_mox:
mox_obj.UnsetStubs()
+ if cleanup_stubout:
+ stubout_obj.UnsetAll()
+ stubout_obj.SmartUnsetAll()
if cleanup_mox:
mox_obj.VerifyAll()
new_method.__name__ = func.__name__
@@ -1630,9 +2041,10 @@ class MoxMetaTestBase(type):
class MoxTestBase(unittest.TestCase):
"""Convenience test class to make stubbing easier.
- Sets up a "mox" attribute which is an instance of Mox - any mox tests will
- want this. Also automatically unsets any stubs and verifies that all mock
- methods have been called at the end of each test, eliminating boilerplate
+ Sets up a "mox" attribute which is an instance of Mox (any mox tests will
+ want this), and a "stubs" attribute that is an instance of StubOutForTesting
+ (needed at times). Also automatically unsets any stubs and verifies that all
+ mock methods have been called at the end of each test, eliminating boilerplate
code.
"""
@@ -1641,3 +2053,4 @@ class MoxTestBase(unittest.TestCase):
def setUp(self):
super(MoxTestBase, self).setUp()
self.mox = Mox()
+ self.stubs = stubout.StubOutForTesting()
diff --git a/mox_test.py b/mox_test.py
index bf806f6..f865fb9 100755
--- a/mox_test.py
+++ b/mox_test.py
@@ -24,6 +24,7 @@ import mox
import mox_test_helper
+OS_LISTDIR = mox_test_helper.os.listdir
class ExpectedMethodCallsErrorTest(unittest.TestCase):
"""Test creation and string conversion of ExpectedMethodCallsError."""
@@ -100,6 +101,35 @@ class AndTest(unittest.TestCase):
self.failIf(mox.And(mox.In("NOTFOUND"),
mox.ContainsKeyValue("mock", "obj")) == test_dict)
+class FuncTest(unittest.TestCase):
+ """Test Func correctly evaluates based upon true-false return."""
+
+ def testFuncTrueFalseEvaluation(self):
+ """Should return True if the validating function returns True."""
+ equals_one = lambda x: x == 1
+ always_none = lambda x: None
+
+ self.assert_(mox.Func(equals_one) == 1)
+ self.failIf(mox.Func(equals_one) == 0)
+
+
+ self.failIf(mox.Func(always_none) == 1)
+ self.failIf(mox.Func(always_none) == 0)
+ self.failIf(mox.Func(always_none) == None)
+
+ def testFuncExceptionPropagation(self):
+ """Exceptions within the validating function should propagate."""
+ class TestException(Exception):
+ pass
+
+ def raiseExceptionOnNotOne(value):
+ if value != 1:
+ raise TestException
+ else:
+ return True
+
+ self.assert_(mox.Func(raiseExceptionOnNotOne) == 1)
+ self.assertRaises(TestException, mox.Func(raiseExceptionOnNotOne).__eq__, 2)
class SameElementsAsTest(unittest.TestCase):
"""Test SameElementsAs correctly identifies sequences with same elements."""
@@ -129,6 +159,19 @@ class SameElementsAsTest(unittest.TestCase):
"""Should return False if two lists with unhashable elements are unequal."""
self.failIf(mox.SameElementsAs([{'a': 1}, {2: 'b'}]) == [{2: 'b'}])
+ def testActualIsNotASequence(self):
+ """Should return False if the actual object is not a sequence."""
+ self.failIf(mox.SameElementsAs([1]) == object())
+
+ def testOneUnhashableObjectInActual(self):
+ """Store the entire iterator for a correct comparison.
+
+ In a previous version of SameElementsAs, iteration stopped when an
+ unhashable object was encountered and then was restarted, so the actual list
+ appeared smaller than it was.
+ """
+ self.failIf(mox.SameElementsAs([1, 2]) == iter([{}, 1, 2]))
+
class ContainsKeyValueTest(unittest.TestCase):
"""Test ContainsKeyValue correctly identifies key/value pairs in a dict.
@@ -147,6 +190,32 @@ class ContainsKeyValueTest(unittest.TestCase):
self.failIf(mox.ContainsKeyValue("qux", 1) == {"key": 2})
+class ContainsAttributeValueTest(unittest.TestCase):
+ """Test ContainsAttributeValue correctly identifies properties in an object.
+ """
+
+ def setUp(self):
+ """Create an object to test with."""
+
+
+ class TestObject(object):
+ key = 1
+
+ self.test_object = TestObject()
+
+ def testValidPair(self):
+ """Should return True if the object has the key attribute and it matches."""
+ self.assert_(mox.ContainsAttributeValue("key", 1) == self.test_object)
+
+ def testInvalidValue(self):
+ """Should return False if the value is not correct."""
+ self.failIf(mox.ContainsKeyValue("key", 2) == self.test_object)
+
+ def testInvalidKey(self):
+ """Should return False if they the object doesn't have the property."""
+ self.failIf(mox.ContainsKeyValue("qux", 1) == self.test_object)
+
+
class InTest(unittest.TestCase):
"""Test In correctly identifies a key in a list/dict"""
@@ -158,6 +227,19 @@ class InTest(unittest.TestCase):
"""Should return True if the item is a key in a dict."""
self.assert_(mox.In("test") == {"test" : "module"})
+ def testItemInTuple(self):
+ """Should return True if the item is in the list."""
+ self.assert_(mox.In(1) == (1, 2, 3))
+
+ def testTupleInTupleOfTuples(self):
+ self.assert_(mox.In((1, 2, 3)) == ((1, 2, 3), (1, 2)))
+
+ def testItemNotInList(self):
+ self.failIf(mox.In(1) == [2, 3])
+
+ def testTupleNotInTupleOfTuples(self):
+ self.failIf(mox.In((1, 2)) == ((1, 2, 3), (4, 5)))
+
class NotTest(unittest.TestCase):
"""Test Not correctly identifies False predicates."""
@@ -232,6 +314,52 @@ class RegexTest(unittest.TestCase):
"<regular expression 'a\s+b', flags=4>")
+class IsTest(unittest.TestCase):
+ """Verify Is correctly checks equality based upon identity, not value"""
+
+ class AlwaysComparesTrue(object):
+ def __eq__(self, other):
+ return True
+ def __cmp__(self, other):
+ return 0
+ def __ne__(self, other):
+ return False
+
+ def testEqualityValid(self):
+ o1 = self.AlwaysComparesTrue()
+ self.assertTrue(mox.Is(o1), o1)
+
+ def testEqualityInvalid(self):
+ o1 = self.AlwaysComparesTrue()
+ o2 = self.AlwaysComparesTrue()
+ self.assertTrue(o1 == o2)
+ # but...
+ self.assertFalse(mox.Is(o1) == o2)
+
+ def testInequalityValid(self):
+ o1 = self.AlwaysComparesTrue()
+ o2 = self.AlwaysComparesTrue()
+ self.assertTrue(mox.Is(o1) != o2)
+
+ def testInequalityInvalid(self):
+ o1 = self.AlwaysComparesTrue()
+ self.assertFalse(mox.Is(o1) != o1)
+
+ def testEqualityInListValid(self):
+ o1 = self.AlwaysComparesTrue()
+ o2 = self.AlwaysComparesTrue()
+ isa_list = [mox.Is(o1), mox.Is(o2)]
+ str_list = [o1, o2]
+ self.assertTrue(isa_list == str_list)
+
+ def testEquailtyInListInvalid(self):
+ o1 = self.AlwaysComparesTrue()
+ o2 = self.AlwaysComparesTrue()
+ isa_list = [mox.Is(o1), mox.Is(o2)]
+ mixed_list = [o2, o1]
+ self.assertFalse(isa_list == mixed_list)
+
+
class IsATest(unittest.TestCase):
"""Verify IsA correctly checks equality based upon class type, not value."""
@@ -294,6 +422,37 @@ class IsAlmostTest(unittest.TestCase):
self.assertNotEquals(mox.IsAlmost('1.8999999999'), '1.9')
+class ValueRememberTest(unittest.TestCase):
+ """Verify comparing argument against remembered value."""
+
+ def testValueEquals(self):
+ """Verify that value will compare to stored value."""
+ value = mox.Value()
+ value.store_value('hello world')
+ self.assertEquals(value, 'hello world')
+
+ def testNoValue(self):
+ """Verify that uninitialized value does not compare to "empty" values."""
+ value = mox.Value()
+ self.assertNotEquals(value, None)
+ self.assertNotEquals(value, False)
+ self.assertNotEquals(value, 0)
+ self.assertNotEquals(value, '')
+ self.assertNotEquals(value, ())
+ self.assertNotEquals(value, [])
+ self.assertNotEquals(value, {})
+ self.assertNotEquals(value, object())
+ self.assertNotEquals(value, set())
+
+ def testRememberValue(self):
+ """Verify that comparing against remember will store argument."""
+ value = mox.Value()
+ remember = mox.Remember(value)
+ self.assertNotEquals(value, 'hello world') # value not yet stored.
+ self.assertEquals(remember, 'hello world') # store value here.
+ self.assertEquals(value, 'hello world') # compare against stored value.
+
+
class MockMethodTest(unittest.TestCase):
"""Test class to verify that the MockMethod class is working correctly."""
@@ -302,6 +461,10 @@ class MockMethodTest(unittest.TestCase):
self.mock_method = mox.MockMethod("testMethod", [self.expected_method],
True)
+ def testNameAttribute(self):
+ """Should provide a __name__ attribute."""
+ self.assertEquals('testMethod', self.mock_method.__name__)
+
def testAndReturnNoneByDefault(self):
"""Should return None by default."""
return_value = self.mock_method(['original'])
@@ -330,6 +493,34 @@ class MockMethodTest(unittest.TestCase):
self.mock_method(local_list)
self.assertEquals('mutation', local_list[0])
+ def testWithReturningSideEffects(self):
+ """Should call state modifier and propagate its return value."""
+ local_list = ['original']
+ expected_return = 'expected_return'
+ def modifier_with_return(mutable_list):
+ self.assertTrue(local_list is mutable_list)
+ mutable_list[0] = 'mutation'
+ return expected_return
+ self.expected_method.WithSideEffects(modifier_with_return)
+ actual_return = self.mock_method(local_list)
+ self.assertEquals('mutation', local_list[0])
+ self.assertEquals(expected_return, actual_return)
+
+ def testWithReturningSideEffectsWithAndReturn(self):
+ """Should call state modifier and ignore its return value."""
+ local_list = ['original']
+ expected_return = 'expected_return'
+ unexpected_return = 'unexpected_return'
+ def modifier_with_return(mutable_list):
+ self.assertTrue(local_list is mutable_list)
+ mutable_list[0] = 'mutation'
+ return unexpected_return
+ self.expected_method.WithSideEffects(modifier_with_return).AndReturn(
+ expected_return)
+ actual_return = self.mock_method(local_list)
+ self.assertEquals('mutation', local_list[0])
+ self.assertEquals(expected_return, actual_return)
+
def testEqualityNoParamsEqual(self):
"""Methods with the same name and without params should be equal."""
expected_method = mox.MockMethod("testMethod", [], False)
@@ -431,6 +622,13 @@ class MockAnythingTest(unittest.TestCase):
"""Calling repr on a MockAnything instance must work."""
self.assertEqual('<MockAnything instance>', repr(self.mock_object))
+ def testCanMockStr(self):
+ self.mock_object.__str__().AndReturn("foo");
+ self.mock_object._Replay()
+ actual = str(self.mock_object)
+ self.mock_object._Verify();
+ self.assertEquals("foo", actual)
+
def testSetupMode(self):
"""Verify the mock will accept any call."""
self.mock_object.NonsenseCall()
@@ -931,6 +1129,7 @@ class MockObjectTest(unittest.TestCase):
self.assertEqual([x for x in dummy], ['X', 'Y'])
dummy._Verify()
+
def testMockContains_ExpectedContains_Success(self):
"""Test that __contains__ gets mocked in Dummy.
@@ -1030,6 +1229,20 @@ class MockObjectTest(unittest.TestCase):
self.assertEquals(['a', 'b'], [x for x in dummy])
dummy._Verify()
+ def testInstantiationWithAdditionalAttributes(self):
+ mock_object = mox.MockObject(TestClass, attrs={"attr1": "value"})
+ self.assertEquals(mock_object.attr1, "value")
+
+ def testCantOverrideMethodsWithAttributes(self):
+ self.assertRaises(ValueError, mox.MockObject, TestClass,
+ attrs={"ValidCall": "value"})
+
+ def testCantMockNonPublicAttributes(self):
+ self.assertRaises(mox.PrivateAttributeError, mox.MockObject, TestClass,
+ attrs={"_protected": "value"})
+ self.assertRaises(mox.PrivateAttributeError, mox.MockObject, TestClass,
+ attrs={"__private": "value"})
+
class MoxTest(unittest.TestCase):
"""Verify Mox works correctly."""
@@ -1069,6 +1282,27 @@ class MoxTest(unittest.TestCase):
self.assertEquals("yes", ret_val)
self.mox.VerifyAll()
+ def testSignatureMatchingWithComparatorAsFirstArg(self):
+ """Test that the first argument can be a comparator."""
+
+ def VerifyLen(val):
+ """This will raise an exception when not given a list.
+
+ This exception will be raised when trying to infer/validate the
+ method signature.
+ """
+ return len(val) != 1
+
+ mock_obj = self.mox.CreateMock(TestClass)
+ # This intentionally does not name the 'nine' param so it triggers
+ # deeper inspection.
+ mock_obj.MethodWithArgs(mox.Func(VerifyLen), mox.IgnoreArg(), None)
+ self.mox.ReplayAll()
+
+ mock_obj.MethodWithArgs([1, 2], "foo", None)
+
+ self.mox.VerifyAll()
+
def testCallableObject(self):
"""Test recording calls to a callable object works."""
mock_obj = self.mox.CreateMock(CallableClass)
@@ -1102,6 +1336,11 @@ class MoxTest(unittest.TestCase):
self.assertRaises(mox.UnexpectedMethodCallError, mock_obj, "ZOOBAZ")
+ def testCallableObjectVerifiesSignature(self):
+ mock_obj = self.mox.CreateMock(CallableClass)
+ # Too many arguments
+ self.assertRaises(AttributeError, mock_obj, "foo", "bar")
+
def testUnorderedGroup(self):
"""Test that using one unordered group works."""
mock_obj = self.mox.CreateMockAnything()
@@ -1392,11 +1631,14 @@ class MoxTest(unittest.TestCase):
self.mox.VerifyAll()
def testStubOutMethod(self):
- """Test that a method is replaced with a MockAnything."""
+ """Test that a method is replaced with a MockObject."""
test_obj = TestClass()
+ method_type = type(test_obj.OtherValidCall)
# Replace OtherValidCall with a mock.
self.mox.StubOutWithMock(test_obj, 'OtherValidCall')
- self.assert_(isinstance(test_obj.OtherValidCall, mox.MockAnything))
+ self.assertTrue(isinstance(test_obj.OtherValidCall, mox.MockObject))
+ self.assertFalse(type(test_obj.OtherValidCall) is method_type)
+
test_obj.OtherValidCall().AndReturn('foo')
self.mox.ReplayAll()
@@ -1405,7 +1647,289 @@ class MoxTest(unittest.TestCase):
self.mox.VerifyAll()
self.mox.UnsetStubs()
self.assertEquals('foo', actual)
- self.failIf(isinstance(test_obj.OtherValidCall, mox.MockAnything))
+ self.assertTrue(type(test_obj.OtherValidCall) is method_type)
+
+ def testStubOutMethod_Unbound_Comparator(self):
+ instance = TestClass()
+ self.mox.StubOutWithMock(TestClass, 'OtherValidCall')
+
+ TestClass.OtherValidCall(mox.IgnoreArg()).AndReturn('foo')
+ self.mox.ReplayAll()
+
+ actual = TestClass.OtherValidCall(instance)
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+ self.assertEquals('foo', actual)
+
+ def testStubOutMethod_Unbound_Subclass_Comparator(self):
+ self.mox.StubOutWithMock(mox_test_helper.TestClassFromAnotherModule, 'Value')
+ mox_test_helper.TestClassFromAnotherModule.Value(
+ mox.IsA(mox_test_helper.ChildClassFromAnotherModule)).AndReturn('foo')
+ self.mox.ReplayAll()
+
+ instance = mox_test_helper.ChildClassFromAnotherModule()
+ actual = mox_test_helper.TestClassFromAnotherModule.Value(instance)
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+ self.assertEquals('foo', actual)
+
+ def testStubOuMethod_Unbound_WithOptionalParams(self):
+ self.mox = mox.Mox()
+ self.mox.StubOutWithMock(TestClass, 'OptionalArgs')
+ TestClass.OptionalArgs(mox.IgnoreArg(), foo=2)
+ self.mox.ReplayAll()
+
+ t = TestClass()
+ TestClass.OptionalArgs(t, foo=2)
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+
+ def testStubOutMethod_Unbound_ActualInstance(self):
+ instance = TestClass()
+ self.mox.StubOutWithMock(TestClass, 'OtherValidCall')
+
+ TestClass.OtherValidCall(instance).AndReturn('foo')
+ self.mox.ReplayAll()
+
+ actual = TestClass.OtherValidCall(instance)
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+ self.assertEquals('foo', actual)
+
+ def testStubOutMethod_Unbound_DifferentInstance(self):
+ instance = TestClass()
+ self.mox.StubOutWithMock(TestClass, 'OtherValidCall')
+
+ TestClass.OtherValidCall(instance).AndReturn('foo')
+ self.mox.ReplayAll()
+
+ # This should fail, since the instances are different
+ self.assertRaises(mox.UnexpectedMethodCallError,
+ TestClass.OtherValidCall, "wrong self")
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+
+ def testStubOutMethod_Unbound_NamedUsingPositional(self):
+ """Check positional parameters can be matched to keyword arguments."""
+ self.mox.StubOutWithMock(mox_test_helper.ExampleClass, 'NamedParams')
+ instance = mox_test_helper.ExampleClass()
+ mox_test_helper.ExampleClass.NamedParams(instance, 'foo', baz=None)
+ self.mox.ReplayAll()
+
+ mox_test_helper.ExampleClass.NamedParams(instance, 'foo', baz=None)
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+
+ def testStubOutMethod_Unbound_NamedUsingPositional_SomePositional(self):
+ """Check positional parameters can be matched to keyword arguments."""
+ self.mox.StubOutWithMock(mox_test_helper.ExampleClass, 'TestMethod')
+ instance = mox_test_helper.ExampleClass()
+ mox_test_helper.ExampleClass.TestMethod(instance, 'one', 'two', 'nine')
+ self.mox.ReplayAll()
+
+ mox_test_helper.ExampleClass.TestMethod(instance, 'one', 'two', 'nine')
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+
+ def testStubOutMethod_Unbound_SpecialArgs(self):
+ self.mox.StubOutWithMock(mox_test_helper.ExampleClass, 'SpecialArgs')
+ instance = mox_test_helper.ExampleClass()
+ mox_test_helper.ExampleClass.SpecialArgs(instance, 'foo', None, bar='bar')
+ self.mox.ReplayAll()
+
+ mox_test_helper.ExampleClass.SpecialArgs(instance, 'foo', None, bar='bar')
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+
+ def testStubOutMethod_Bound_SimpleTest(self):
+ t = self.mox.CreateMock(TestClass)
+
+ t.MethodWithArgs(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn('foo')
+ self.mox.ReplayAll()
+
+ actual = t.MethodWithArgs(None, None);
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+ self.assertEquals('foo', actual)
+
+ def testStubOutMethod_Bound_NamedUsingPositional(self):
+ """Check positional parameters can be matched to keyword arguments."""
+ self.mox.StubOutWithMock(mox_test_helper.ExampleClass, 'NamedParams')
+ instance = mox_test_helper.ExampleClass()
+ instance.NamedParams('foo', baz=None)
+ self.mox.ReplayAll()
+
+ instance.NamedParams('foo', baz=None)
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+
+ def testStubOutMethod_Bound_NamedUsingPositional_SomePositional(self):
+ """Check positional parameters can be matched to keyword arguments."""
+ self.mox.StubOutWithMock(mox_test_helper.ExampleClass, 'TestMethod')
+ instance = mox_test_helper.ExampleClass()
+ instance.TestMethod(instance, 'one', 'two', 'nine')
+ self.mox.ReplayAll()
+
+ instance.TestMethod(instance, 'one', 'two', 'nine')
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+
+ def testStubOutMethod_Bound_SpecialArgs(self):
+ self.mox.StubOutWithMock(mox_test_helper.ExampleClass, 'SpecialArgs')
+ instance = mox_test_helper.ExampleClass()
+ instance.SpecialArgs(instance, 'foo', None, bar='bar')
+ self.mox.ReplayAll()
+
+ instance.SpecialArgs(instance, 'foo', None, bar='bar')
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+
+ def testStubOutMethod_Func_PropgatesExceptions(self):
+ """Errors in a Func comparator should propagate to the calling method."""
+ class TestException(Exception):
+ pass
+
+ def raiseExceptionOnNotOne(value):
+ if value == 1:
+ return True
+ else:
+ raise TestException
+
+ test_obj = TestClass()
+ self.mox.StubOutWithMock(test_obj, 'MethodWithArgs')
+ test_obj.MethodWithArgs(
+ mox.IgnoreArg(), mox.Func(raiseExceptionOnNotOne)).AndReturn(1)
+ test_obj.MethodWithArgs(
+ mox.IgnoreArg(), mox.Func(raiseExceptionOnNotOne)).AndReturn(1)
+ self.mox.ReplayAll()
+
+ self.assertEqual(test_obj.MethodWithArgs('ignored', 1), 1)
+ self.assertRaises(TestException,
+ test_obj.MethodWithArgs, 'ignored', 2)
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+
+
+ def testStubOut_SignatureMatching_init_(self):
+ self.mox.StubOutWithMock(mox_test_helper.ExampleClass, '__init__')
+ mox_test_helper.ExampleClass.__init__(mox.IgnoreArg())
+ self.mox.ReplayAll()
+
+ # Create an instance of a child class, which calls the parent
+ # __init__
+ mox_test_helper.ChildExampleClass()
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+
+ def testStubOutClass_OldStyle(self):
+ """Test a mocked class whose __init__ returns a Mock."""
+ self.mox.StubOutWithMock(mox_test_helper, 'TestClassFromAnotherModule')
+ self.assert_(isinstance(mox_test_helper.TestClassFromAnotherModule,
+ mox.MockObject))
+
+ mock_instance = self.mox.CreateMock(
+ mox_test_helper.TestClassFromAnotherModule)
+ mox_test_helper.TestClassFromAnotherModule().AndReturn(mock_instance)
+ mock_instance.Value().AndReturn('mock instance')
+
+ self.mox.ReplayAll()
+
+ a_mock = mox_test_helper.TestClassFromAnotherModule()
+ actual = a_mock.Value()
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+ self.assertEquals('mock instance', actual)
+
+ def testStubOutClass(self):
+ self.mox.StubOutClassWithMocks(mox_test_helper, 'CallableClass')
+
+ # Instance one
+ mock_one = mox_test_helper.CallableClass(1, 2)
+ mock_one.Value().AndReturn('mock')
+
+ # Instance two
+ mock_two = mox_test_helper.CallableClass(8, 9)
+ mock_two('one').AndReturn('called mock')
+
+ self.mox.ReplayAll()
+
+ one = mox_test_helper.CallableClass(1, 2)
+ actual_one = one.Value()
+
+ two = mox_test_helper.CallableClass(8, 9)
+ actual_two = two('one')
+
+ self.mox.VerifyAll()
+ self.mox.UnsetStubs()
+
+ # Verify the correct mocks were returned
+ self.assertEquals(mock_one, one)
+ self.assertEquals(mock_two, two)
+
+ # Verify
+ self.assertEquals('mock', actual_one)
+ self.assertEquals('called mock', actual_two)
+
+ def testStubOutClass_NotAClass(self):
+ self.assertRaises(TypeError, self.mox.StubOutClassWithMocks,
+ mox_test_helper, 'MyTestFunction')
+
+ def testStubOutClassNotEnoughCreated(self):
+ self.mox.StubOutClassWithMocks(mox_test_helper, 'CallableClass')
+
+ mox_test_helper.CallableClass(1, 2)
+ mox_test_helper.CallableClass(8, 9)
+
+ self.mox.ReplayAll()
+ mox_test_helper.CallableClass(1, 2)
+
+ self.assertRaises(mox.ExpectedMockCreationError, self.mox.VerifyAll)
+ self.mox.UnsetStubs()
+
+ def testStubOutClassWrongSignature(self):
+ self.mox.StubOutClassWithMocks(mox_test_helper, 'CallableClass')
+
+ self.assertRaises(AttributeError, mox_test_helper.CallableClass)
+
+ self.mox.UnsetStubs()
+
+ def testStubOutClassWrongParameters(self):
+ self.mox.StubOutClassWithMocks(mox_test_helper, 'CallableClass')
+
+ mox_test_helper.CallableClass(1, 2)
+
+ self.mox.ReplayAll()
+
+ self.assertRaises(mox.UnexpectedMethodCallError,
+ mox_test_helper.CallableClass, 8, 9)
+ self.mox.UnsetStubs()
+
+ def testStubOutClassTooManyCreated(self):
+ self.mox.StubOutClassWithMocks(mox_test_helper, 'CallableClass')
+
+ mox_test_helper.CallableClass(1, 2)
+
+ self.mox.ReplayAll()
+ mox_test_helper.CallableClass(1, 2)
+ self.assertRaises(mox.UnexpectedMockCreationError,
+ mox_test_helper.CallableClass, 8, 9)
+
+ self.mox.UnsetStubs()
def testWarnsUserIfMockingMock(self):
"""Test that user is warned if they try to stub out a MockAnything."""
@@ -1413,6 +1937,37 @@ class MoxTest(unittest.TestCase):
self.assertRaises(TypeError, self.mox.StubOutWithMock, TestClass,
'MyStaticMethod')
+ def testStubOutFirstClassMethodVerifiesSignature(self):
+ self.mox.StubOutWithMock(mox_test_helper, 'MyTestFunction')
+
+ # Wrong number of arguments
+ self.assertRaises(AttributeError, mox_test_helper.MyTestFunction, 1)
+ self.mox.UnsetStubs()
+
+ def _testMethodSignatureVerification(self, stubClass):
+ # If stubClass is true, the test is run against an a stubbed out class,
+ # else the test is run against a stubbed out instance.
+ if stubClass:
+ self.mox.StubOutWithMock(mox_test_helper.ExampleClass, "TestMethod")
+ obj = mox_test_helper.ExampleClass()
+ else:
+ obj = mox_test_helper.ExampleClass()
+ self.mox.StubOutWithMock(mox_test_helper.ExampleClass, "TestMethod")
+ self.assertRaises(AttributeError, obj.TestMethod)
+ self.assertRaises(AttributeError, obj.TestMethod, 1)
+ self.assertRaises(AttributeError, obj.TestMethod, nine=2)
+ obj.TestMethod(1, 2)
+ obj.TestMethod(1, 2, 3)
+ obj.TestMethod(1, 2, nine=3)
+ self.assertRaises(AttributeError, obj.TestMethod, 1, 2, 3, 4)
+ self.mox.UnsetStubs()
+
+ def testStubOutClassMethodVerifiesSignature(self):
+ self._testMethodSignatureVerification(stubClass=True)
+
+ def testStubOutObjectMethodVerifiesSignature(self):
+ self._testMethodSignatureVerification(stubClass=False)
+
def testStubOutObject(self):
"""Test than object is replaced with a Mock."""
@@ -1464,12 +2019,14 @@ class MoxTestBaseTest(unittest.TestCase):
def setUp(self):
self.mox = mox.Mox()
self.test_mox = mox.Mox()
+ self.test_stubs = mox.stubout.StubOutForTesting()
self.result = unittest.TestResult()
def tearDown(self):
- # In case one of our tests fail before UnsetStubs is called.
self.mox.UnsetStubs()
self.test_mox.UnsetStubs()
+ self.test_stubs.UnsetAll()
+ self.test_stubs.SmartUnsetAll()
def _setUpTestClass(self):
"""Replacement for setUp in the test class instance.
@@ -1479,6 +2036,7 @@ class MoxTestBaseTest(unittest.TestCase):
in the test class instance.
"""
self.test.mox = self.test_mox
+ self.test.stubs = self.test_stubs
def _CreateTest(self, test_name):
"""Create a test from our example mox class.
@@ -1492,14 +2050,17 @@ class MoxTestBaseTest(unittest.TestCase):
"""Run the checks to confirm test method completed successfully."""
self.mox.StubOutWithMock(self.test_mox, 'UnsetStubs')
self.mox.StubOutWithMock(self.test_mox, 'VerifyAll')
+ self.mox.StubOutWithMock(self.test_stubs, 'UnsetAll')
+ self.mox.StubOutWithMock(self.test_stubs, 'SmartUnsetAll')
self.test_mox.UnsetStubs()
self.test_mox.VerifyAll()
+ self.test_stubs.UnsetAll()
+ self.test_stubs.SmartUnsetAll()
self.mox.ReplayAll()
self.test.run(result=self.result)
self.assertTrue(self.result.wasSuccessful())
- self.mox.UnsetStubs()
self.mox.VerifyAll()
- self.test_mox.UnsetStubs()
+ self.mox.UnsetStubs() # Needed to call the real VerifyAll() below.
self.test_mox.VerifyAll()
def testSuccess(self):
@@ -1507,47 +2068,78 @@ class MoxTestBaseTest(unittest.TestCase):
self._CreateTest('testSuccess')
self._VerifySuccess()
+ def testSuccessNoMocks(self):
+ """Let testSuccess() unset all the mocks, and verify they've been unset."""
+ self._CreateTest('testSuccess')
+ self.test.run(result=self.result)
+ self.assertTrue(self.result.wasSuccessful())
+ self.assertEqual(OS_LISTDIR, mox_test_helper.os.listdir)
+
+ def testStubs(self):
+ """Test that "self.stubs" is provided as is useful."""
+ self._CreateTest('testHasStubs')
+ self._VerifySuccess()
+
+ def testStubsNoMocks(self):
+ """Let testHasStubs() unset the stubs by itself."""
+ self._CreateTest('testHasStubs')
+ self.test.run(result=self.result)
+ self.assertTrue(self.result.wasSuccessful())
+ self.assertEqual(OS_LISTDIR, mox_test_helper.os.listdir)
+
def testExpectedNotCalled(self):
"""Stubbed out method is not called."""
self._CreateTest('testExpectedNotCalled')
self.mox.StubOutWithMock(self.test_mox, 'UnsetStubs')
- # Dont stub out VerifyAll - that's what causes the test to fail
+ self.mox.StubOutWithMock(self.test_stubs, 'UnsetAll')
+ self.mox.StubOutWithMock(self.test_stubs, 'SmartUnsetAll')
+ # Don't stub out VerifyAll - that's what causes the test to fail
self.test_mox.UnsetStubs()
- self.test_mox.VerifyAll()
+ self.test_stubs.UnsetAll()
+ self.test_stubs.SmartUnsetAll()
self.mox.ReplayAll()
self.test.run(result=self.result)
self.failIf(self.result.wasSuccessful())
- self.mox.UnsetStubs()
self.mox.VerifyAll()
- self.test_mox.UnsetStubs()
+
+ def testExpectedNotCalledNoMocks(self):
+ """Let testExpectedNotCalled() unset all the mocks by itself."""
+ self._CreateTest('testExpectedNotCalled')
+ self.test.run(result=self.result)
+ self.failIf(self.result.wasSuccessful())
+ self.assertEqual(OS_LISTDIR, mox_test_helper.os.listdir)
def testUnexpectedCall(self):
"""Stubbed out method is called with unexpected arguments."""
self._CreateTest('testUnexpectedCall')
self.mox.StubOutWithMock(self.test_mox, 'UnsetStubs')
+ self.mox.StubOutWithMock(self.test_stubs, 'UnsetAll')
+ self.mox.StubOutWithMock(self.test_stubs, 'SmartUnsetAll')
# Ensure no calls are made to VerifyAll()
self.mox.StubOutWithMock(self.test_mox, 'VerifyAll')
self.test_mox.UnsetStubs()
+ self.test_stubs.UnsetAll()
+ self.test_stubs.SmartUnsetAll()
self.mox.ReplayAll()
self.test.run(result=self.result)
self.failIf(self.result.wasSuccessful())
- self.mox.UnsetStubs()
self.mox.VerifyAll()
- self.test_mox.UnsetStubs()
def testFailure(self):
"""Failing assertion in test method."""
self._CreateTest('testFailure')
self.mox.StubOutWithMock(self.test_mox, 'UnsetStubs')
+ self.mox.StubOutWithMock(self.test_stubs, 'UnsetAll')
+ self.mox.StubOutWithMock(self.test_stubs, 'SmartUnsetAll')
# Ensure no calls are made to VerifyAll()
self.mox.StubOutWithMock(self.test_mox, 'VerifyAll')
self.test_mox.UnsetStubs()
+ self.test_stubs.UnsetAll()
+ self.test_stubs.SmartUnsetAll()
self.mox.ReplayAll()
self.test.run(result=self.result)
self.failIf(self.result.wasSuccessful())
- self.mox.UnsetStubs()
self.mox.VerifyAll()
- self.test_mox.UnsetStubs()
def testMixin(self):
"""Run test from mix-in test class, ensure it passes."""
@@ -1601,6 +2193,12 @@ class MyTestCase(unittest.TestCase):
def setUp(self):
super(MyTestCase, self).setUp()
self.critical_variable = 42
+ self.another_critical_variable = 42
+
+ def testMethodOverride(self):
+ """Should be properly overriden in a derived class."""
+ self.assertEquals(42, self.another_critical_variable)
+ self.another_critical_variable += 1
class MoxTestBaseMultipleInheritanceTest(mox.MoxTestBase, MyTestCase):
@@ -1608,12 +2206,26 @@ class MoxTestBaseMultipleInheritanceTest(mox.MoxTestBase, MyTestCase):
def setUp(self):
super(MoxTestBaseMultipleInheritanceTest, self).setUp()
+ self.another_critical_variable = 99
def testMultipleInheritance(self):
"""Should be able to access members created by all parent setUp()."""
self.assert_(isinstance(self.mox, mox.Mox))
self.assertEquals(42, self.critical_variable)
+ def testMethodOverride(self):
+ """Should run before MyTestCase.testMethodOverride."""
+ self.assertEquals(99, self.another_critical_variable)
+ self.another_critical_variable = 42
+ super(MoxTestBaseMultipleInheritanceTest, self).testMethodOverride()
+ self.assertEquals(43, self.another_critical_variable)
+
+class MoxTestDontMockProperties(MoxTestBaseTest):
+ def testPropertiesArentMocked(self):
+ mock_class = self.mox.CreateMock(ClassWithProperties)
+ self.assertRaises(mox.UnknownMethodCallError, lambda:
+ mock_class.prop_attr)
+
class TestClass:
"""This class is used only for testing the mock framework"""
@@ -1633,9 +2245,15 @@ class TestClass:
def ValidCall(self):
pass
+ def MethodWithArgs(self, one, two, nine=None):
+ pass
+
def OtherValidCall(self):
pass
+ def OptionalArgs(self, foo='boom'):
+ pass
+
def ValidCallWithArgs(self, *args, **kwargs):
pass
@@ -1674,6 +2292,7 @@ class TestClass:
def __iter__(self):
pass
+
class ChildClass(TestClass):
"""This inherits from TestClass."""
def __init__(self):
@@ -1692,6 +2311,15 @@ class CallableClass(object):
def __call__(self, param):
return param
+class ClassWithProperties(object):
+ def setter_attr(self, value):
+ pass
+
+ def getter_attr(self):
+ pass
+
+ prop_attr = property(getter_attr, setter_attr)
+
class SubscribtableNonIterableClass(object):
def __getitem__(self, index):
diff --git a/mox_test_helper.py b/mox_test_helper.py
index 164c0ec..5ac989f 100755
--- a/mox_test_helper.py
+++ b/mox_test_helper.py
@@ -74,3 +74,69 @@ class ExampleMoxTest(mox.MoxTestBase, ExampleMoxTestMixin):
os.stat(self.DIR_PATH)
self.mox.ReplayAll()
os.stat(self.DIR_PATH)
+
+ def testHasStubs(self):
+ listdir_list = []
+
+ def MockListdir(directory):
+ listdir_list.append(directory)
+
+ self.stubs.Set(os, 'listdir', MockListdir)
+ os.listdir(self.DIR_PATH)
+ self.assertEqual([self.DIR_PATH], listdir_list)
+
+
+class TestClassFromAnotherModule(object):
+
+ def __init__(self):
+ return None
+
+ def Value(self):
+ return 'Not mock'
+
+
+class ChildClassFromAnotherModule(TestClassFromAnotherModule):
+ """A child class of TestClassFromAnotherModule.
+
+ Used to test stubbing out unbound methods, where child classes
+ are eventually bound.
+ """
+
+ def __init__(self):
+ TestClassFromAnotherModule.__init__(self)
+
+
+class CallableClass(object):
+
+ def __init__(self, one, two, nine=None):
+ pass
+
+ def __call__(self, one):
+ return 'Not mock'
+
+ def Value():
+ return 'Not mock'
+
+
+def MyTestFunction(one, two, nine=None):
+ pass
+
+
+class ExampleClass(object):
+ def __init__(self, foo='bar'):
+ pass
+
+ def TestMethod(self, one, two, nine=None):
+ pass
+
+ def NamedParams(self, ignore, foo='bar', baz='qux'):
+ pass
+
+ def SpecialArgs(self, *args, **kwargs):
+ pass
+
+
+# This class is used to test stubbing out __init__ of a parent class.
+class ChildExampleClass(ExampleClass):
+ def __init__(self):
+ ExampleClass.__init__(self)
diff --git a/setup.py b/setup.py
index e1bf142..3c7dbaa 100755
--- a/setup.py
+++ b/setup.py
@@ -1,8 +1,23 @@
#!/usr/bin/python2.4
+#
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
from distutils.core import setup
setup(name='mox',
- version='0.5.1',
+ version='0.5.3',
py_modules=['mox', 'stubout'],
url='http://code.google.com/p/pymox/',
maintainer='pymox maintainers',
diff --git a/stubout_testee.py b/stubout_testee.py
index 9cbdef6..0ae29ab 100644
--- a/stubout_testee.py
+++ b/stubout_testee.py
@@ -1,2 +1,16 @@
+# Copyright 2008 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
def SampleFunction():
raise Exception('I should never be called!')