diff -Nru python-mox-0.5.1/debian/changelog python-mox-0.5.3/debian/changelog --- python-mox-0.5.1/debian/changelog 2010-03-06 11:38:56.000000000 +0000 +++ python-mox-0.5.3/debian/changelog 2010-05-24 11:32:32.000000000 +0100 @@ -1,3 +1,9 @@ +python-mox (0.5.3-1) unstable; urgency=low + + * New upstream version + + -- Iustin Pop Mon, 24 May 2010 12:30:06 +0200 + python-mox (0.5.1-2) unstable; urgency=low * Remove build-deps on -dev package (not needed) diff -Nru python-mox-0.5.1/mox.py python-mox-0.5.3/mox.py --- python-mox-0.5.1/mox.py 2009-05-05 23:52:58.000000000 +0100 +++ python-mox-0.5.3/mox.py 2010-04-30 21:56:05.000000000 +0100 @@ -33,6 +33,11 @@ ensures that every expected method was called; otherwise, an exception will be raised. +WARNING! Mock objects created by Mox are not thread-safe. If you are +call a mock in multiple threads, it should be guarded by a mutex. + +TODO(stevepm): Add the option to make mocks thread-safe! + Suggested usage / workflow: # Create Mox factory @@ -57,6 +62,7 @@ """ from collections import deque +import difflib import inspect import re import types @@ -117,12 +123,17 @@ """ Error.__init__(self) - self._unexpected_method = unexpected_method - self._expected = expected + if expected is None: + self._str = "Unexpected method call %s" % (unexpected_method,) + else: + differ = difflib.Differ() + diff = differ.compare(str(unexpected_method).splitlines(True), + str(expected).splitlines(True)) + self._str = ("Unexpected method call. unexpected:- expected:+\n%s" + % ("\n".join(diff),)) def __str__(self): - return "Unexpected method call: %s. Expecting: %s" % \ - (self._unexpected_method, self._expected) + return self._str class UnknownMethodCallError(Error): @@ -145,13 +156,84 @@ 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] + + # 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.""" @@ -159,28 +241,32 @@ self._mock_objects = [] self.stubs = stubout.StubOutForTesting() - def CreateMock(self, class_to_mock): + def CreateMock(self, class_to_mock, attrs={}): """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) + new_mock = MockObject(class_to_mock, attrs=attrs) self._mock_objects.append(new_mock) return new_mock - def CreateMockAnything(self): + def CreateMockAnything(self, description=None): """Create a mock that will accept any method calls. This does not enforce an interface. - """ - new_mock = MockAnything() + Args: + description: str. Optionally, a descriptive name for the mock object being + created, for debugging output purposes. + """ + new_mock = MockAnything(description=description) self._mock_objects.append(new_mock) return new_mock @@ -218,13 +304,71 @@ """ attr_to_replace = getattr(obj, attr_name) - if type(attr_to_replace) in self._USE_MOCK_OBJECT and not use_mock_anything: + 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 in self._USE_MOCK_OBJECT and not use_mock_anything: stub = self.CreateMock(attr_to_replace) else: - stub = self.CreateMockAnything() + stub = self.CreateMockAnything(description='Stub for %s' % attr_to_replace) 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.""" @@ -269,15 +413,21 @@ This is helpful for mocking classes that do not provide a public interface. """ - def __init__(self): - """ """ + def __init__(self, description=None): + """Initialize a new MockAnything. + + Args: + description: str. Optionally, a descriptive name for the mock object being + created, for debugging output purposes. + """ + self._description = description self._Reset() def __str__(self): return "" % id(self) def __repr__(self): - return self.__str__() + return '' def __getattr__(self, method_name): """Intercept method calls on this object. @@ -310,7 +460,8 @@ """ return MockMethod(method_name, self._expected_calls_queue, - self._replay_mode, method_to_mock=method_to_mock) + self._replay_mode, method_to_mock=method_to_mock, + description=self._description) def __nonzero__(self): """Return 1 for nonzero so the mock can be used as a conditional.""" @@ -365,7 +516,7 @@ 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={}): """Initialize a mock object. This determines the methods and properties of the class and stores them. @@ -373,6 +524,12 @@ 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. """ # This is used to hack around the mixin/inheritance of MockAnything, which @@ -383,12 +540,36 @@ self._known_methods = set() self._known_vars = set() self._class_to_mock = class_to_mock + try: + self._description = class_to_mock.__name__ + # If class_to_mock is a mock itself, then we'll get an UnknownMethodCall + # error here from the underlying call to __getattr__('__name__') + except (UnknownMethodCallError, AttributeError): + try: + self._description = type(class_to_mock).__name__ + except AttributeError: + 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. @@ -449,10 +630,8 @@ __setitem__. """ - setitem = self._class_to_mock.__dict__.get('__setitem__', None) - # Verify the class supports item assignment. - if setitem is None: + if '__setitem__' not in dir(self._class_to_mock): raise TypeError('object does not support item assignment') # If we are in replay mode then simply call the mock __setitem__ method. @@ -477,13 +656,11 @@ Raises: TypeError if the underlying class is not subscriptable. UnexpectedMethodCallError if the object does not expect the call to - __setitem__. + __getitem__. """ - getitem = self._class_to_mock.__dict__.get('__getitem__', None) - # Verify the class supports item assignment. - if getitem is None: + if '__getitem__' not in dir(self._class_to_mock): raise TypeError('unsubscriptable object') # If we are in replay mode then simply call the mock __getitem__ method. @@ -495,6 +672,47 @@ # Otherwise, create a mock method __getitem__. return self._CreateMockMethod('__getitem__')(key) + def __iter__(self): + """Provide custom logic for mocking classes that are iterable. + + Returns: + Expected return value in replay mode. A MockMethod object for the + __iter__ method that has already been called if not in replay mode. + + Raises: + TypeError if the underlying class is not iterable. + UnexpectedMethodCallError if the object does not expect the call to + __iter__. + + """ + methods = dir(self._class_to_mock) + + # Verify the class supports iteration. + if '__iter__' not in methods: + # If it doesn't have iter method and we are in replay method, then try to + # iterate using subscripts. + if '__getitem__' not in methods or not self._replay_mode: + raise TypeError('not iterable object') + else: + results = [] + index = 0 + try: + while True: + results.append(self[index]) + index += 1 + except IndexError: + return iter(results) + + # If we are in replay mode then simply call the mock __iter__ method. + if self._replay_mode: + return MockMethod('__iter__', self._expected_calls_queue, + self._replay_mode)() + + + # Otherwise, create a mock method __iter__. + return self._CreateMockMethod('__iter__')() + + def __contains__(self, key): """Provide custom logic for mocking classes that contain items. @@ -525,14 +743,23 @@ def __call__(self, *params, **named_params): """Provide custom logic for mocking classes that are callable.""" - # Verify the class we are mocking is callable - callable = self._class_to_mock.__dict__.get('__call__', None) - if callable is None: + # Verify the class we are mocking is callable. + callable = hasattr(self._class_to_mock, '__call__') + if not callable: raise TypeError('Not callable') # 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) == types.FunctionType: + 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 @@ -542,6 +769,54 @@ return self._class_to_mock +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): """Ensures that methods are called correctly.""" @@ -644,7 +919,8 @@ signature) matches the expected method. """ - def __init__(self, method_name, call_queue, replay_mode, method_to_mock=None): + def __init__(self, method_name, call_queue, replay_mode, + method_to_mock=None, description=None): """Construct a new mock method. Args: @@ -654,17 +930,22 @@ # replay_mode: False if we are recording, True if we are verifying calls # against the call queue. # method_to_mock: The actual method being mocked, used for introspection. + # description: optionally, a descriptive name for this method. Typically + # this is equal to the descriptive name of the method's class. method_name: str call_queue: list or deque replay_mode: bool method_to_mock: a method object + description: str or None """ 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) self._replay_mode = replay_mode + self._description = description self._params = None self._named_params = None @@ -702,7 +983,9 @@ 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 @@ -715,6 +998,16 @@ raise AttributeError('MockMethod has no attribute "%s". ' 'Did you remember to put your mocks in replay mode?' % name) + def __iter__(self): + """Raise a TypeError with a helpful message.""" + raise TypeError('MockMethod cannot be iterated. ' + 'Did you remember to put your mocks in replay mode?') + + def next(self): + """Raise a TypeError with a helpful message.""" + raise TypeError('MockMethod cannot be iterated. ' + 'Did you remember to put your mocks in replay mode?') + def _PopNextMethod(self): """Pop the next method from our call queue.""" try: @@ -753,8 +1046,10 @@ params = ', '.join( [repr(p) for p in self._params or []] + ['%s=%r' % x for x in sorted((self._named_params or {}).items())]) - desc = "%s(%s) -> %r" % (self._name, params, self._return_value) - return desc + full_desc = "%s(%s) -> %r" % (self._name, params, self._return_value) + if self._description: + full_desc = "%s.%s" % (self._description, full_desc) + return full_desc def __eq__(self, rhs): """Test whether this MockMethod is equivalent to another MockMethod. @@ -1173,6 +1468,37 @@ return '' % (self._key, 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). @@ -1442,7 +1768,7 @@ def __init__(self, group_name): super(MultipleTimesGroup, self).__init__(group_name) self._methods = set() - self._methods_called = set() + self._methods_left = set() def AddMethod(self, mock_method): """Add a method to this group. @@ -1452,6 +1778,7 @@ """ self._methods.add(mock_method) + self._methods_left.add(mock_method) def MethodCalled(self, mock_method): """Remove a method call from the group. @@ -1471,10 +1798,9 @@ # Check to see if this method exists, and if so add it to the set of # called methods. - for method in self._methods: if method == mock_method: - self._methods_called.add(mock_method) + self._methods_left.discard(method) # Always put this group back on top of the queue, because we don't know # when we are done. mock_method._call_queue.appendleft(self) @@ -1488,18 +1814,7 @@ def IsSatisfied(self): """Return True if all methods in this group are called at least once.""" - # NOTE(psycho): We can't use the simple set difference here because we want - # to match different parameters which are considered the same e.g. IsA(str) - # and some string. This solution is O(n^2) but n should be small. - tmp = self._methods.copy() - for called in self._methods_called: - for expected in tmp: - if called == expected: - tmp.remove(expected) - if not tmp: - return True - break - return False + return len(self._methods_left) == 0 class MoxMetaTestBase(type): @@ -1519,7 +1834,8 @@ # 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): @@ -1541,14 +1857,21 @@ """ 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__ @@ -1560,9 +1883,10 @@ 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. """ @@ -1571,3 +1895,4 @@ def setUp(self): super(MoxTestBase, self).setUp() self.mox = Mox() + self.stubs = stubout.StubOutForTesting() diff -Nru python-mox-0.5.1/mox_test_helper.py python-mox-0.5.3/mox_test_helper.py --- python-mox-0.5.1/mox_test_helper.py 2009-03-20 16:56:11.000000000 +0000 +++ python-mox-0.5.3/mox_test_helper.py 2010-04-30 21:57:47.000000000 +0100 @@ -74,3 +74,38 @@ 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 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 diff -Nru python-mox-0.5.1/mox_test.py python-mox-0.5.3/mox_test.py --- python-mox-0.5.1/mox_test.py 2009-05-05 23:52:58.000000000 +0100 +++ python-mox-0.5.3/mox_test.py 2010-04-30 21:56:56.000000000 +0100 @@ -24,6 +24,7 @@ import mox_test_helper +OS_LISTDIR = mox_test_helper.os.listdir class ExpectedMethodCallsErrorTest(unittest.TestCase): """Test creation and string conversion of ExpectedMethodCallsError.""" @@ -147,6 +148,32 @@ 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""" @@ -269,6 +296,7 @@ stringIO = cStringIO.StringIO() self.assert_(isA == stringIO) + class IsAlmostTest(unittest.TestCase): """Verify IsAlmost correctly checks equality of floating point numbers.""" @@ -292,6 +320,7 @@ self.assertNotEquals(mox.IsAlmost('1.8999999999'), 1.9) self.assertNotEquals(mox.IsAlmost('1.8999999999'), '1.9') + class MockMethodTest(unittest.TestCase): """Test class to verify that the MockMethod class is working correctly.""" @@ -300,6 +329,10 @@ 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']) @@ -328,6 +361,34 @@ 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) @@ -425,6 +486,10 @@ def setUp(self): self.mock_object = mox.MockAnything() + def testRepr(self): + """Calling repr on a MockAnything instance must work.""" + self.assertEqual('', repr(self.mock_object)) + def testSetupMode(self): """Verify the mock will accept any call.""" self.mock_object.NonsenseCall() @@ -793,10 +858,7 @@ self.assertRaises(mox.ExpectedMethodCallsError, dummy._Verify) def testMockSetItem_ExpectedNoSetItem_Success(self): - """Test that __setitem__() gets mocked in Dummy. - - In this test, _Verify() succeeds. - """ + """Test that __setitem__() gets mocked in Dummy.""" dummy = mox.MockObject(TestClass) # NOT doing dummy['X'] = 'Y' @@ -805,8 +867,6 @@ def call(): dummy['X'] = 'Y' self.assertRaises(mox.UnexpectedMethodCallError, call) - dummy._Verify() - def testMockSetItem_ExpectedNoSetItem_NoSuccess(self): """Test that __setitem__() gets mocked in Dummy. @@ -834,8 +894,25 @@ dummy._Verify() + def testMockSetItem_WithSubClassOfNewStyleClass(self): + class NewStyleTestClass(object): + def __init__(self): + self.my_dict = {} + + def __setitem__(self, key, value): + self.my_dict[key], value + + class TestSubClass(NewStyleTestClass): + pass + + dummy = mox.MockObject(TestSubClass) + dummy[1] = 2 + dummy._Replay() + dummy[1] = 2 + dummy._Verify() + def testMockGetItem_ExpectedGetItem_Success(self): - """Test that __setitem__() gets mocked in Dummy. + """Test that __getitem__() gets mocked in Dummy. In this test, _Verify() succeeds. """ @@ -849,7 +926,7 @@ dummy._Verify() def testMockGetItem_ExpectedGetItem_NoSuccess(self): - """Test that __setitem__() gets mocked in Dummy. + """Test that __getitem__() gets mocked in Dummy. In this test, _Verify() fails. """ @@ -863,10 +940,7 @@ self.assertRaises(mox.ExpectedMethodCallsError, dummy._Verify) def testMockGetItem_ExpectedNoGetItem_NoSuccess(self): - """Test that __setitem__() gets mocked in Dummy. - - In this test, _Verify() succeeds. - """ + """Test that __getitem__() gets mocked in Dummy.""" dummy = mox.MockObject(TestClass) # NOT doing dummy['X'] @@ -875,10 +949,8 @@ def call(): return dummy['X'] self.assertRaises(mox.UnexpectedMethodCallError, call) - dummy._Verify() - def testMockGetItem_ExpectedGetItem_NonmatchingParameters(self): - """Test that __setitem__() fails if other parameters are expected.""" + """Test that __getitem__() fails if other parameters are expected.""" dummy = mox.MockObject(TestClass) dummy['X'].AndReturn('value') @@ -890,6 +962,35 @@ dummy._Verify() + def testMockGetItem_WithSubClassOfNewStyleClass(self): + class NewStyleTestClass(object): + def __getitem__(self, key): + return {1: '1', 2: '2'}[key] + + class TestSubClass(NewStyleTestClass): + pass + + dummy = mox.MockObject(TestSubClass) + dummy[1].AndReturn('3') + + dummy._Replay() + self.assertEquals('3', dummy.__getitem__(1)) + dummy._Verify() + + def testMockIter_ExpectedIter_Success(self): + """Test that __iter__() gets mocked in Dummy. + + In this test, _Verify() succeeds. + """ + dummy = mox.MockObject(TestClass) + iter(dummy).AndReturn(iter(['X', 'Y'])) + + dummy._Replay() + + self.assertEqual([x for x in dummy], ['X', 'Y']) + + dummy._Verify() + def testMockContains_ExpectedContains_Success(self): """Test that __contains__ gets mocked in Dummy. @@ -931,6 +1032,79 @@ dummy._Verify() + def testMockIter_ExpectedIter_NoSuccess(self): + """Test that __iter__() gets mocked in Dummy. + + In this test, _Verify() fails. + """ + dummy = mox.MockObject(TestClass) + iter(dummy).AndReturn(iter(['X', 'Y'])) + + dummy._Replay() + + # NOT doing self.assertEqual([x for x in dummy], ['X', 'Y']) + + self.assertRaises(mox.ExpectedMethodCallsError, dummy._Verify) + + def testMockIter_ExpectedNoIter_NoSuccess(self): + """Test that __iter__() gets mocked in Dummy.""" + dummy = mox.MockObject(TestClass) + # NOT doing iter(dummy) + + dummy._Replay() + + def call(): return [x for x in dummy] + self.assertRaises(mox.UnexpectedMethodCallError, call) + + def testMockIter_ExpectedGetItem_Success(self): + """Test that __iter__() gets mocked in Dummy using getitem.""" + dummy = mox.MockObject(SubscribtableNonIterableClass) + dummy[0].AndReturn('a') + dummy[1].AndReturn('b') + dummy[2].AndRaise(IndexError) + + dummy._Replay() + self.assertEquals(['a', 'b'], [x for x in dummy]) + dummy._Verify() + + def testMockIter_ExpectedNoGetItem_NoSuccess(self): + """Test that __iter__() gets mocked in Dummy using getitem.""" + dummy = mox.MockObject(SubscribtableNonIterableClass) + # NOT doing dummy[index] + + dummy._Replay() + function = lambda: [x for x in dummy] + self.assertRaises(mox.UnexpectedMethodCallError, function) + + def testMockGetIter_WithSubClassOfNewStyleClass(self): + class NewStyleTestClass(object): + def __iter__(self): + return iter([1, 2, 3]) + + class TestSubClass(NewStyleTestClass): + pass + + dummy = mox.MockObject(TestSubClass) + iter(dummy).AndReturn(iter(['a', 'b'])) + dummy._Replay() + 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.""" @@ -979,6 +1153,16 @@ self.assertEquals("qux", ret_val) self.mox.VerifyAll() + def testInheritedCallableObject(self): + """Test recording calls to an object inheriting from a callable object.""" + mock_obj = self.mox.CreateMock(InheritsFromCallable) + mock_obj("foo").AndReturn("qux") + self.mox.ReplayAll() + + ret_val = mock_obj("foo") + self.assertEquals("qux", ret_val) + self.mox.VerifyAll() + def testCallOnNonCallableObject(self): """Test that you cannot call a non-callable object.""" mock_obj = self.mox.CreateMock(TestClass) @@ -992,6 +1176,11 @@ 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() @@ -1106,13 +1295,13 @@ mock_obj.Method(3) mock_obj.Method(3) + self.mox.VerifyAll() + self.assertEquals(9, actual_one) self.assertEquals(9, second_one) # Repeated calls should return same number. self.assertEquals(10, actual_two) self.assertEquals(42, actual_three) - self.mox.VerifyAll() - def testMultipleTimesUsingIsAParameter(self): """Test if MultipleTimesGroup works with a IsA parameter.""" mock_obj = self.mox.CreateMockAnything() @@ -1126,11 +1315,40 @@ second_one = mock_obj.Method("2") # This tests MultipleTimes. mock_obj.Close() + self.mox.VerifyAll() + self.assertEquals(9, actual_one) self.assertEquals(9, second_one) # Repeated calls should return same number. + def testMutlipleTimesUsingFunc(self): + """Test that the Func is not evaluated more times than necessary. + + If a Func() has side effects, it can cause a passing test to fail. + """ + + self.counter = 0 + def MyFunc(actual_str): + """Increment the counter if actual_str == 'foo'.""" + if actual_str == 'foo': + self.counter += 1 + return True + + mock_obj = self.mox.CreateMockAnything() + mock_obj.Open() + mock_obj.Method(mox.Func(MyFunc)).MultipleTimes() + mock_obj.Close() + self.mox.ReplayAll() + + mock_obj.Open() + mock_obj.Method('foo') + mock_obj.Method('foo') + mock_obj.Method('not-foo') + mock_obj.Close() + self.mox.VerifyAll() + self.assertEquals(2, self.counter) + def testMultipleTimesThreeMethods(self): """Test if MultipleTimesGroup works with three or more methods.""" mock_obj = self.mox.CreateMockAnything() @@ -1268,6 +1486,115 @@ self.assertEquals('foo', actual) self.failIf(isinstance(test_obj.OtherValidCall, mox.MockAnything)) + 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.""" + self.mox.StubOutWithMock(TestClass, 'MyStaticMethod') + 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 testStubOutObject(self): """Test than object is replaced with a Mock.""" @@ -1301,6 +1628,7 @@ self.assertEquals('MockMethod has no attribute "ShowMeTheMoney". ' 'Did you remember to put your mocks in replay mode?', str(e)) + class ReplayTest(unittest.TestCase): """Verify Replay works properly.""" @@ -1311,18 +1639,21 @@ mox.Replay(mock_obj) self.assertTrue(mock_obj._replay_mode) + class MoxTestBaseTest(unittest.TestCase): """Verify that all tests in a class derived from MoxTestBase are wrapped.""" 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. @@ -1332,6 +1663,7 @@ 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. @@ -1345,14 +1677,17 @@ """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): @@ -1360,47 +1695,78 @@ 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.""" @@ -1454,6 +1820,12 @@ 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): @@ -1461,12 +1833,26 @@ 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""" @@ -1486,6 +1872,9 @@ def ValidCall(self): pass + def MethodWithArgs(self, one, two, nine=None): + pass + def OtherValidCall(self): pass @@ -1524,6 +1913,9 @@ """Returns True if d contains the key.""" return key in self.d + def __iter__(self): + pass + class ChildClass(TestClass): """This inherits from TestClass.""" @@ -1543,6 +1935,26 @@ 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): + raise IndexError + + +class InheritsFromCallable(CallableClass): + """This class should also be mockable; it inherits from a callable class.""" + + pass + if __name__ == '__main__': unittest.main() diff -Nru python-mox-0.5.1/PKG-INFO python-mox-0.5.3/PKG-INFO --- python-mox-0.5.1/PKG-INFO 2009-05-06 00:20:20.000000000 +0100 +++ python-mox-0.5.3/PKG-INFO 2010-05-04 20:51:13.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: mox -Version: 0.5.1 +Version: 0.5.3 Summary: Mock object framework Home-page: http://code.google.com/p/pymox/ Author: pymox maintainers diff -Nru python-mox-0.5.1/setup.py python-mox-0.5.3/setup.py --- python-mox-0.5.1/setup.py 2009-05-06 00:17:33.000000000 +0100 +++ python-mox-0.5.3/setup.py 2010-05-04 20:49:26.000000000 +0100 @@ -2,7 +2,7 @@ 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',