diff options
author | Przemyslaw Gajda <quermit@gmail.com> | 2012-04-22 17:45:03 +0200 |
---|---|---|
committer | Przemyslaw Gajda <quermit@gmail.com> | 2012-04-22 17:45:03 +0200 |
commit | 3becdfd4a0be62be9d09a744fe74c7636f4f5754 (patch) | |
tree | 6f61e060cbd67250ebe11b1ae12d5ac10ad7489f | |
parent | e6127210cc59271fd2ed02c6a6ceb11b7efd63af (diff) | |
download | pymox-3becdfd4a0be62be9d09a744fe74c7636f4f5754.tar.gz |
Updated to version 0.5.3
-rwxr-xr-x | mox.py | 505 | ||||
-rwxr-xr-x | mox_test.py | 656 | ||||
-rwxr-xr-x | mox_test_helper.py | 66 | ||||
-rwxr-xr-x | setup.py | 17 | ||||
-rw-r--r-- | stubout_testee.py | 14 |
5 files changed, 1197 insertions, 61 deletions
@@ -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) @@ -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!') |