pyDoubles-1.4/0000755000175000017500000000000011651314476014032 5ustar carlosblecarlosblepyDoubles-1.4/PKG-INFO0000644000175000017500000000054511651314476015133 0ustar carlosblecarlosbleMetadata-Version: 1.0 Name: pyDoubles Version: 1.4 Summary: Test doubles framework for Python Home-page: https://bitbucket.org/carlosble/pydoubles Author: Carlos Ble Author-email: carlos@iexpertos.com License: LICENSE.txt Description: Please read the documentation in the project's home: http://www.pydoubles.org Platform: UNKNOWN pyDoubles-1.4/docs/0000755000175000017500000000000011651314476014762 5ustar carlosblecarlosblepyDoubles-1.4/docs/docs.txt0000644000175000017500000000142411566551132016451 0ustar carlosblecarlosbleDocumentation: To mock a void method: when(spy.method).then_return(None) when(spy.method).with_args(x).then_return(None) RECORDED calls were << ['args = ()', 'kwargs= {'key_param': 'foo'}] >>, EXPECTED call has << args = ('bar',), keyword args = No keyword args where passed in >> This means that during the execution, the method was called like this: some_method(key_param = 'foo') but the assertion made in the test was this: some_method('bar') keyword args are the arguments which have a name (optional parameters). If you want to return several values, use a list or tuple: when(self.rec.one_arg_method).then_return(1,2) when(self.rec.one_arg_method).then_return((1,2,3)) More docs: http://xunitpatterns.com/Test%20Spy.html http://hammingweight.com/UsingSpyObjects.pdf pyDoubles-1.4/package.txt0000644000175000017500000000006111600170250016143 0ustar carlosblecarlosblehttp://guide.python-distribute.org/creation.html pyDoubles-1.4/pyDoubles/0000755000175000017500000000000011651314476016000 5ustar carlosblecarlosblepyDoubles-1.4/pyDoubles/__init__.py0000644000175000017500000000000011567667744020116 0ustar carlosblecarlosblepyDoubles-1.4/pyDoubles/safeunicode.py0000644000175000017500000000123611575175237020646 0ustar carlosblecarlosble# -*- coding: utf-8 -*- def __if_number_get_string(number): converted_str = number if isinstance(number, int) or \ isinstance(number, float): converted_str = str(number) return converted_str def get_unicode(strOrUnicode, encoding='utf-8'): strOrUnicode = __if_number_get_string(strOrUnicode) if isinstance(strOrUnicode, unicode): return strOrUnicode return unicode(strOrUnicode, encoding, errors='ignore') def get_string(strOrUnicode, encoding='utf-8'): strOrUnicode = __if_number_get_string(strOrUnicode) if isinstance(strOrUnicode, unicode): return strOrUnicode.encode(encoding) return strOrUnicodepyDoubles-1.4/pyDoubles/matchers.py0000644000175000017500000000471311611062054020151 0ustar carlosblecarlosble""" Authors: Carlos Ble (www.carlosble.com) Ruben Bernardez (www.rubenbp.com) www.iExpertos.com License: Apache 2 (http://www.apache.org/licenses/LICENSE-2.0.html) Project home: https://bitbucket.org/carlosble/pydoubles """ import safeunicode import re from core import WrongApiUsage # PUBLIC API def str_containing(substr): return _SubstrMatcher_(substr) def str_not_containing(substr): return _NotSubstrMatcher_(substr) def str_length(arg): return _LengthMatcher_(arg) def obj_with_fields(arg): return _ObjWithFieldsMatcher_(arg) # CORE API class PyDoublesMatcher(object): matcher_name = "pyDoublesMatcher" def __str__(self): try: return self.matcher_name + " '" + str(safeunicode.get_string(self.defined_arg)) + "'" except: return str(self.__class__) def matches(self, received_arg): return False class _LengthMatcher_(PyDoublesMatcher): matcher_name = "string with length" def __init__(self, length): self.defined_arg = length def matches(self, text): return int(self.defined_arg) == len(text) class _SubstrMatcher_(PyDoublesMatcher): matcher_name = "string containing" def __init__(self, substring): self.defined_arg = self.__prepare_arg(substring) def __prepare_arg(self, substring): return safeunicode.get_string(substring) def matches(self, arg2): try: self.arg2 = self.__prepare_arg(arg2) return self._comparison() except TypeError: return False def _comparison(self): return re.search(self.defined_arg, self.arg2) class _NotSubstrMatcher_(_SubstrMatcher_): matcher_name = "string NOT containing" def __init__(self, substring): super(_NotSubstrMatcher_, self).__init__(substring) def _comparison(self): return not re.search(self.defined_arg, self.arg2) class _ObjWithFieldsMatcher_(PyDoublesMatcher): matcher_name = "Object with certain value in the field" def __init__(self, field_dict): if not hasattr(field_dict, "iteritems"): raise WrongApiUsage("This matcher requires a dictionary: {'name': value} ") self.defined_arg = field_dict def matches(self, arg): for field_name, field_value in self.defined_arg.iteritems(): if getattr(arg, field_name) != field_value: return False return True pyDoubles-1.4/pyDoubles/framework.py0000644000175000017500000002576711651312744020364 0ustar carlosblecarlosble""" Authors: www.iExpertos.com - Carlos Ble (www.carlosble.com) License: Apache 2 (http://www.apache.org/licenses/LICENSE-2.0.html) Project home: http://www.pydoubles.org """ from core import _CallsRepository_, _StubsRepository_ from core import _EmptyObject_, _Introspector_ from core import _DoubleMethodHandler_, _MirrorMethodHandler_, _RaiserMethodHandler_, _HardcodedMethodHandler_ from core import UnexpectedBehavior, WrongApiUsage, ApiMismatch from core import ArgsDontMatch, ANY_ARG class ProxySpy(object): """ This test double is just an _interceptor to the original object. It watches the calls to the original, recording what happens so that we can make assertions on the calls that were made. The actual methods in the original object are executed, they are not mocked or stubbed by default. """ def __init__(self, original_instance): self.calls = _CallsRepository_() self.introspector = _Introspector_() self.stubs = _StubsRepository_() self.original_instance = original_instance self.original_instance._double = self self.asserting_on_method = None self.invoked_method_name = None def __getattr__(self, attr): return _DoubleMethodHandler_(self, attr) def _before_method(self, args, kwargs): self._replace_possible_alias_method_name() self.calls.register_call(self.invoked_method_name, *args, **kwargs) def __remove_interceptor_from_call_args(self, args): return args[1:] def _on_method_call(self, *args, **kwargs): args = self.__remove_interceptor_from_call_args(args) self._before_method(args, kwargs) if self.stubs.is_stub_for_this_input(self.invoked_method_name, args, kwargs): return self.stubs.return_value_given_input( self.invoked_method_name, args, kwargs) return self._invoke_method(args, kwargs) def _invoke_method(self, args, kwargs): original_method = getattr(self.original_instance, self.invoked_method_name) return original_method(*args, **kwargs) def _was_called(self, method): return self._assert_called( self.introspector.method_name(method)) def _assert_called(self, method_name): self.expected_args = self.expected_kwargs = None if self.calls.was_registered(method_name): return self else: raise UnexpectedBehavior("The method was not called:" + method_name) def was_called(self): return self._assert_called(self._name_of_method_under_assertion()) def was_never_called(self): if not self.calls.was_registered(self._name_of_method_under_assertion()): return self else: raise UnexpectedBehavior( "The method was called indeed:" + self._name_of_method_under_assertion()) def _is_asserting_that_call_was_made(self): return self.asserting_on_method is not None def with_args(self, *args, **kwargs): if self._is_asserting_that_call_was_made(): self.expected_args = args self.expected_kwargs = kwargs self.calls.assert_match_call( self._name_of_method_under_assertion(), *args, **kwargs) else: self.stubs.set_input_for_last_stubbed_method(args, kwargs) return self def times(self, times): self._assert_that_is_at_least_twice(times) if self.calls.was_registered( self._name_of_method_under_assertion(), times, self.expected_args, self.expected_kwargs): return self else: raise UnexpectedBehavior( "The method was not called %s times: %s" % (str(times), self._name_of_method_under_assertion())) def _assert_that_is_at_least_twice(self, times): if times <= 1: raise WrongApiUsage('Times cant be less than 2. For just one time, do not specify times. For zero times, use a spy with was_never_called') def _name_of_method_under_assertion(self): return self.introspector.method_name(self.asserting_on_method) def stub_out(self, method): self.stubs.create_stub(self.introspector.method_name(method)) def then_return(self, args): self.stubs.set_output_for_last_stubbed_method(args) return self def then_raise(self, exception_instance): self.stubs.set_output_for_last_stubbed_method(method_raising(exception_instance)) def then_return_input(self): self.stubs.set_output_for_last_stubbed_method(_MirrorMethodHandler_()) def _replace_possible_alias_method_name(self): for method_name in self.stubs.stubbed_method_names(): if self.introspector.are_synonymous(self.original_instance, self.invoked_method_name, method_name): self.invoked_method_name = method_name return class Spy(ProxySpy): """ Works like a ProxySpy but methods are stubbed returning nothing by default, rather than calling the doubled object. The spy will check if the API consumed by the user matches the actual API in the doubled object. """ DEFAULT_RETURN_VALUE_FOR_STUBBED_METHOD = None def _api_mismatch_msg(self, original_args, args): return "The number of arguments in the actual object <%s> don't match the actual call on the double <%s>" % ( str(tuple(original_args)).replace("'self', ", ""), str(args)) def _was_called(self, method): method_name = self.introspector.method_name(method) if not hasattr(self.original_instance, method_name): raise ApiMismatch('Object instance has no method %s' % method_name) return super(Spy, self)._was_called(method) def _check_api_match(self, args, kwargs): original_method = getattr(self.original_instance, self.invoked_method_name) args_plus_kwargs_spec_len, kwargs_spec_len, arg_spec = \ self.introspector.original_method_signature(original_method) if len(args) + len(kwargs) != args_plus_kwargs_spec_len and \ len(args) != args_plus_kwargs_spec_len - kwargs_spec_len: msg = self._api_mismatch_msg(arg_spec.args, args) raise ApiMismatch(msg) def _is_not_an_empty_spy(self): return not hasattr(self.original_instance, "_empty_object__") def _before_method(self, args, kwargs): super(Spy, self)._before_method(args, kwargs) if self._is_not_an_empty_spy(): self._check_api_match(args, kwargs) def _invoke_method(self, args, kwargs): return self.DEFAULT_RETURN_VALUE_FOR_STUBBED_METHOD class Mock(Spy): """ Any method that is going to be invoked on this object must be expected through the expect_call method before invoking it. Otherwise, an exception will be thrown. """ def __init__(self, obj_instance): super(Mock, self).__init__(obj_instance) self.satisfied_expectations = _StubsRepository_() def add_expectation(self, method): self.stub_out(method) def _before_method(self, args, kwargs): super(Mock, self)._before_method(args, kwargs) if self.stubs.is_stub_for_this_input(self.invoked_method_name, args, kwargs): self.satisfied_expectations.add_stub(self.invoked_method_name, args, kwargs) else: raise UnexpectedBehavior( "\nThis call wasn't expected:\n '%s[args=%s,kwargs=%s]' .\nExpected calls are:\n %s" % ( self.invoked_method_name, str(args), str(kwargs), self.stubs.show_all_methods())) def returning(self, args): return super(Mock, self).then_return(args) def times(self, times): self._assert_that_is_at_least_twice(times) self.stubs.repeat_stub_times(times) def assert_expectations(self): self.assert_that_is_satisfied() def assert_that_is_satisfied(self): if not self.stubs.repositories_are_equivalent( self.satisfied_expectations): raise UnexpectedBehavior( "\nDefined expectations were not satisfied. \nRegistered calls are:\n %s\nBut expectations are\n %s" % ( self.calls.show_registered_calls(), self.stubs.show_all_methods())) # PUBLIC STATIC METHODS: def proxy_spy(obj_instance): """ Creates a spy objet that records the calls made but passes them to the actual object. """ return ProxySpy(obj_instance) def spy(obj_instance): """ Creates a spy object based on the given object instance """ return Spy(obj_instance) def empty_stub(): """ Creates a stub object in which you can dynamically add any method """ return empty_spy() def stub(obj_instance): """ Creates a stub object based on the given object instance """ return spy(obj_instance) def empty_spy(): """ Creates a spy object but will not check any API match """ return Spy(_EmptyObject_()) def mock(obj_instance): """ Creates a mock objet based on the given object instance """ return Mock(obj_instance) def empty_mock(): """ Creates a mock objet but will not check any API match """ return mock(_EmptyObject_()) def method_returning(return_value): """ Creates a method stub, able to receive anything and return the given return_value """ return _HardcodedMethodHandler_(return_value) def method_raising(exception_instance): """ Creates a method stub, which raises the given exception """ return _RaiserMethodHandler_(exception_instance) def expect_call(method): """ Define behavior in a mock object """ double = _Introspector_().double_instance_from_method(method) double.add_expectation(method) return double def when(method): """ Define behavior in a stub or spy object """ double = _Introspector_().double_instance_from_method(method) double.stub_out(method) return double def assert_that_was_called(method): """ Verify the behavior in a spy. Use this with spies only. For mock objects use: mock_instance.assert_that_is_satisfied() """ try: double = _Introspector_().double_instance_from_method(method) double.asserting_on_method = method return double._was_called(method) except AttributeError, e: raise WrongApiUsage( "Make sure you call assert, passing in a method from a test double: (double.method)") def assert_that_method(method): """ Alternative to assert_that_was_called: assert_that(obj.method).was_called() """ double = _Introspector_().double_instance_from_method(method) double.asserting_on_method = method return doublepyDoubles-1.4/pyDoubles/core.py0000644000175000017500000004217611651312272017304 0ustar carlosblecarlosble""" Authors: www.iExpertos.com - Carlos Ble (www.carlosble.com) License: Apache 2 (http://www.apache.org/licenses/LICENSE-2.0.html) Project home: http://www.pydoubles.org """ import inspect import safeunicode import unittest ANY_ARG = "<___ANY___ARG___>" _FailureException = unittest.TestCase.failureException class UnexpectedBehavior(_FailureException): pass class WrongApiUsage(_FailureException): pass class ArgsDontMatch(_FailureException): pass class ApiMismatch(_FailureException): pass class _MatchFinder_(): def args_dont_match(self, arg1, arg2): return not self._args_match(arg1, arg2) def _args_match(self, arg1, arg2): if self._are_valid_args(arg1, arg2): if self._is_a_matcher_object(arg1): return self._matches(arg1, arg2) if self._is_a_matcher_object(arg2): return self._matches(arg2, arg1) return False def _are_valid_args(self, arg1, arg2): return arg1 is not None and arg2 is not None def _is_a_matcher_object(self, arg): return callable(self._get_match_method(arg)) def _get_match_method(self, arg): return getattr(arg, "matches", None) def _matches(self, arg1, arg2): match_method = self._get_match_method(arg1) return match_method(arg2) class _MethodHandler_(object): """ Internal framework class. When user access a method or any other property in the test double, the framework return an instance of this class, which is callable. So calling this instance with parenthesis triggers the on_method_call event in the test double. """ def callable_action(self, args, kwargs): pass def __call__(self, *args, **kwargs): return self.callable_action(args, kwargs) class _DoubleMethodHandler_(_MethodHandler_): def __init__(self, double, attr): self.double = double self.attr_name = attr def callable_action(self, args, kwargs): self.double.invoked_method_name = self.attr_name return self.double._on_method_call(self, *args, **kwargs) class _HardcodedMethodHandler_(_MethodHandler_): def __init__(self, ret_val): self.ret_val = ret_val def callable_action(self, args, kwargs): return self.ret_val class _MirrorMethodHandler_(_MethodHandler_): def callable_action(self, args, kwargs): return args[0] class _RaiserMethodHandler_(_MethodHandler_): def __init__(self, exception): self.exception = exception def callable_action(self, args, kwargs): raise self.exception class _Introspector_(): """ Internal framework class. Specialized in introspection (reflection) """ def method_name(self, interceptor): return interceptor.attr_name def double_instance_from_method(self, interceptor): try: return interceptor.double except AttributeError,e: raise WrongApiUsage("Make sure you call this framework method passing in a method from a spy or a mock object") def original_method_signature(self, original_method): self_arg = 1 arg_spec = inspect.getargspec(original_method) kargs_spec_len = 0 args_plus_kwargs_spec_len = len(arg_spec.args) - self_arg if arg_spec.defaults is not None: kargs_spec_len = len(arg_spec.defaults) return (args_plus_kwargs_spec_len, kargs_spec_len, arg_spec) def are_synonymous(self, instance, method_name1, method_name2): return getattr(instance, method_name1) == getattr(instance, method_name2) class _IOParams_(): """ Data structure class. Keeps method input and output """ args = kwargs = output = None def set_output(self, output): self.output = output def is_stub_ignoring_args(self): return self.args is None and self.kwargs is None def no_matter_input(self): return self.args is None and self.kwargs is None and \ self.output is not None def __str__(self): return "args=%s,kwargs=%s,output=%s" % (str(self.args), str(self.kwargs), str(self.output)) class _MethodPool_(object): """ Internal framework class. Intended to store methods info: The method name, the input arguments and the output for that input. """ def __init__(self): self.pool = {} self.match_finder = _MatchFinder_() def _are_different_arguments(self, arg1, arg2): return arg1 != arg2 and \ arg1 != ANY_ARG and \ arg2 != ANY_ARG def _every_arg_matches_or_is_any_arg(self, args, last_call_args): for i in range(0, len(args)): defined_arg = args[i] received_arg = last_call_args[i] if self._are_different_arguments(defined_arg, received_arg): if self.match_finder.args_dont_match(defined_arg, received_arg): return False return True def _do_args_tuple_match(self, args, last_call_args): if len(args) != len(last_call_args): return False return self._every_arg_matches_or_is_any_arg(args, last_call_args) def _do_args_match(self, last_call_args, ioparams): if ioparams.args is not None and last_call_args is not None: return self._do_args_tuple_match(ioparams.args, last_call_args) return ioparams.args == last_call_args def _do_kwargs_match(self, kwargs, ioparams): if ioparams.kwargs is not None: return kwargs == ioparams.kwargs else: return True def __not_all_ioparams_match(self, called_collection, local_collection): if len(called_collection) != len(local_collection): return True for i in range(0, len(local_collection)): if not self._do_args_match(called_collection[i].args, local_collection[i]): if local_collection[i].args is not None: return True return False def do_pools_match(self, called_method_pool): for name, ioparams in self.pool.iteritems(): if not called_method_pool.has_method(name) or \ self.__not_all_ioparams_match( called_method_pool.pool[name], ioparams): return False return True def add_method(self, method_name, input_args=None, input_kwargs=None, output=None): if not self.pool.has_key(method_name): self.pool[method_name] = [] ioparams = _IOParams_() ioparams.args = input_args ioparams.kwargs = input_kwargs ioparams.set_output(output) self.pool[method_name].append(ioparams) def has_method(self, method_name): return self.pool.has_key(method_name) def times_called(self, method_name, expected_args=None, expected_kwargs=None): return len(self.matching_ioparams_by_args(expected_args, expected_kwargs, method_name)) def __expected_args_match(self, actual_ioparams, expected_args): if expected_args is None: return True else: return self._do_args_match(expected_args, actual_ioparams) def __expected_kwargs_match(self, actual_ioparams, expected_kwargs): if expected_kwargs is None: return True else: return self._do_kwargs_match(expected_kwargs, actual_ioparams) def matching_ioparams_by_args(self, expected_args, expected_kwargs, method_name): matching_ioparams = [] for ioparams in self.pool[method_name]: if self.__expected_args_match(ioparams,expected_args) and \ self.__expected_kwargs_match(ioparams, expected_kwargs): matching_ioparams.append(ioparams) return matching_ioparams def stubbed_method_names(self): return self.pool.keys() class _StubPool_(_MethodPool_): def matching_ioparams_by_args(self, call_args, call_kwargs, method_name): matching_ioparams = self._exact_matching_ioparams_by_args(call_args, call_kwargs, method_name) for ioparams in self.pool[method_name]: if ioparams.is_stub_ignoring_args(): matching_ioparams.append(ioparams) return matching_ioparams def _exact_matching_ioparams_by_args(self, call_args, call_kwargs, method_name): matching_ioparams = [] for ioparams in self.pool[method_name]: if self._do_args_match(call_args, ioparams) and \ self._do_kwargs_match(call_kwargs, ioparams): matching_ioparams.append(ioparams) return matching_ioparams def _ioparams_for_unspecified_method_input(self, method_name): for ioparams in self.pool[method_name]: if ioparams.no_matter_input(): return ioparams return None def clone_last_ioparams(self, method_name): self.pool[method_name].append(self.pool[method_name][-1]) def input_wasnt_specified(self, method_name): return self._ioparams_for_unspecified_method_input( method_name) is not None def get_output_for_unspecified_method_input(self, method_name): ioparams = self._ioparams_for_unspecified_method_input( method_name) if ioparams is not None: return ioparams.output def get_output_for_specified_method_input(self, args, kwargs, method_name): matching_ioparams = self.matching_ioparams_by_args(args, kwargs, method_name) if len(matching_ioparams) > 0: return matching_ioparams[0].output def set_output_for_last_added_method(self, method_name, output): ioparams = self.pool[method_name][-1] ioparams.set_output(output) self.pool[method_name][-1] = ioparams def set_input_for_last_stubbed_method(self, method_name, input_args=None, input_kwargs=None): ioparams = self.pool[method_name][-1] ioparams.args = input_args ioparams.kwargs = input_kwargs self.pool[method_name][-1] = ioparams class _PoolReport_(): """ Displays the information stored in a MethodPool """ def __init__(self, method_pool): self.methods_pool = method_pool def method_info(self, method_name): return ["(" + str(ioparams) + ")" for ioparams in self.methods_pool.pool[method_name]] def all_stored_methods(self): info = "" for key in self.methods_pool.pool: info = "%s%s, %s" % (key, str(self.method_info(key)), info) if len(info) == 0: return "No one" return info class _CallsRepository_(): """ Internal framework class. Basically a wrapper around the MethodPool, to store calls to methods """ def __init__(self): self.method_pool = _MethodPool_() self.report = _PoolReport_(self.method_pool) def register_call(self, method_name, *args, **kwargs): self.method_pool.add_method(method_name, input_args=args, input_kwargs=kwargs) def was_registered(self, method_name, times=None, args=None, kwargs=None): if times == None: return self.method_pool.has_method(method_name) else: return self.method_pool.times_called(method_name, args, kwargs) == times def _readable_kwargs(self, kwargs_str): if kwargs_str == "{}": return "No keyword args where passed in" return kwargs_str def _format_err_msg(self, method_name, args, kwargs): args_str = ",".join([str(safeunicode.get_string(arg)) for arg in args]) kwargs_str = ",".join([str(safeunicode.get_string(kwarg)) for kwarg in kwargs]) return "RECORDED calls were: << %s >>, \n EXPECTED call is << (args = %s), (keyword args = %s) >>" % ( str(self.report.method_info(method_name)), args_str, self._readable_kwargs(kwargs_str)) def _some_call_matches_this_assertion(self, expected_args, expected_kwargs, method_name): matching_ioparams = self.method_pool.matching_ioparams_by_args(expected_args, expected_kwargs, method_name) return len(matching_ioparams) > 0 def assert_match_call(self, method_name, *args, **kwargs): if not self._some_call_matches_this_assertion( args, kwargs, method_name): raise ArgsDontMatch(self._format_err_msg( method_name, args, kwargs)) def show_registered_calls(self): return self.report.all_stored_methods() class _StubsRepository_(): """ Internal framework class. Pretty much a wrapper around the StubPool class, to store stubs """ UNDEFINED = "undefined_____" def __init__(self): self.method_pool = _StubPool_() self.report = _PoolReport_(self.method_pool) self._clear_stub_definition() def _clear_stub_definition(self): self.last_stubbed_method = None def will_stub_any_input(self): return self._is_stub_for_any_input(self.last_stubbed_method) def _some_stub_matches_this_call(self, call_args, call_kwargs, method_name): matching_ioparams = self.method_pool.matching_ioparams_by_args(call_args, call_kwargs, method_name) return len(matching_ioparams) > 0 def is_stub_for_this_input(self, method_name, args, kwargs): return self.method_pool.has_method(method_name) and \ self._some_stub_matches_this_call(args, kwargs, method_name) def _is_stub_for_any_input(self, method_name): return self.method_pool.has_method(method_name) and \ self.method_pool.input_wasnt_specified(method_name) def return_value_given_input(self, method_name, args, kwargs): if self.is_stub_for_this_input(method_name, args, kwargs): output = self.method_pool.get_output_for_specified_method_input(args, kwargs, method_name) else: if self._is_stub_for_any_input(method_name): output = self.method_pool.get_output_for_unspecified_method_input(method_name) if isinstance(output, _MethodHandler_): return self._execute_output_method(output, method_name, args, kwargs) return output def _execute_output_method(self, method, name, args, kwargs): try: return method(*args, **kwargs) except IndexError: raise ApiMismatch( "Method %s seems to require arguments but haven't been passed in" % ( name,)) def add_stub(self, method_name, args, kwargs): self.create_stub(method_name) self.set_input_for_last_stubbed_method(args, kwargs) def create_stub(self, method_name): self._clear_stub_definition() self.last_input_args = self.last_input_kwargs = None self.last_stubbed_method = method_name self.method_pool.add_method(method_name) def set_input_for_last_stubbed_method(self, args, kwargs): self.last_input_args = args self.last_input_kwargs = kwargs self.method_pool.set_input_for_last_stubbed_method( self.last_stubbed_method, input_args=args, input_kwargs=kwargs) def repeat_stub_times(self, times): for i in range(0, times -1): self.method_pool.clone_last_ioparams(self.last_stubbed_method) def set_output_for_any_input_in_last_stubbed_method(self, output): self.method_pool.add_method(self.last_stubbed_method, output=output) def set_output_for_last_stubbed_method(self, output): self.method_pool.set_output_for_last_added_method(self.last_stubbed_method, output) def show_all_methods(self): return self.report.all_stored_methods() def repositories_are_equivalent(self, repository): return self.method_pool.do_pools_match(repository.method_pool) def stubbed_method_names(self): return self.method_pool.stubbed_method_names() class _EmptyObject_(): """ Internal framework class. Intended to be used as the original_object for empty_spy and empty_mock """ asserting_on_method = None _empty_object__ = True def method(self, *args, **kwargs): pass def __getattr__(self, attr): return attrpyDoubles-1.4/README.txt0000644000175000017500000000011711570633071015522 0ustar carlosblecarlosblePlease read the documentation in the project's home: http://www.pydoubles.org pyDoubles-1.4/LICENSE.txt0000644000175000017500000002174611567010154015657 0ustar carlosblecarlosbleApache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and 2. You must cause any modified files to carry prominent notices stating that You changed the files; and 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS pyDoubles-1.4/pyDoublesTests/0000755000175000017500000000000011651314476017023 5ustar carlosblecarlosblepyDoubles-1.4/pyDoublesTests/__init__.py0000644000175000017500000000000011567010615021114 0ustar carlosblecarlosblepyDoubles-1.4/pyDoublesTests/unit.py0000644000175000017500000007002611651313250020346 0ustar carlosblecarlosble# -*- coding: utf-8 -*- """ Authors: Carlos Ble (www.carlosble.com) Ruben Bernardez (www.rubenbp.com) www.iExpertos.com License: Apache 2 (http://www.apache.org/licenses/LICENSE-2.0.html) Project home: https://bitbucket.org/carlosble/pydoubles """ import unittest from pyDoubles.framework import * from pyDoubles.matchers import * from pyDoubles.core import _Introspector_ from pyDoubles.core import _MethodPool_ class SomeException(Exception): pass class Collaborator(): """ The original object we double in tests """ test_field = "OK" def hello(self): return "hello" def something(self): return "ok" def one_arg_method(self, arg1): return arg1 def two_args_method(self, arg1, arg2): return arg1 + arg2 def kwarg_method(self, key_param=False): return key_param def mixed_method(self, arg1, key_param=False): return key_param + arg1 def void_method(self): pass def method_one(self, arg1): return 1 alias_method = one_arg_method class ProxySpyTests(unittest.TestCase): def setUp(self): self.spy = proxy_spy(Collaborator()) def test_assert_was_called(self): self.spy.hello() assert_that_was_called(self.spy.hello) def test_other_way_of_assert_called(self): self.spy.hello() assert_that_method(self.spy.hello).was_called() def test_assert_was_called_on_any_method(self): self.spy.something() assert_that_was_called(self.spy.something) def test_assert_needs_always_a_method_from_a_double(self): self.failUnlessRaises(WrongApiUsage, assert_that_was_called, self.spy) def test_assert_needs_always_a_method_from_a_double_not_the_original(self): self.failUnlessRaises(WrongApiUsage, assert_that_was_called, Collaborator().hello) def test_one_method_called_other_wasnt(self): self.spy.something() self.failUnlessRaises(UnexpectedBehavior, assert_that_was_called, self.spy.hello) def test_two_methods_called_assert_on_the_first(self): self.spy.hello() self.spy.something() assert_that_was_called(self.spy.hello) def test_get_method_name(self): name = _Introspector_().method_name(self.spy.hello) self.assertEquals("hello", name) def test_call_original_method(self): self.assertEquals("ok", self.spy.something()) def test_get_instance_from_method(self): spy_found = _Introspector_().double_instance_from_method(self.spy.hello) self.assertEquals(self.spy, spy_found) def test_assert_was_called_when_wasnt(self): self.failUnlessRaises(UnexpectedBehavior, assert_that_was_called, self.spy.hello) def test_was_called_with_same_parameters(self): self.spy.one_arg_method(1) assert_that_was_called(self.spy.one_arg_method).with_args(1) def test_was_called_with_same_parameters_in_variables(self): arg1 = 1 self.spy.one_arg_method(arg1) assert_that_was_called(self.spy.one_arg_method).with_args(1) def test_was_called_with_same_parameters_when_not(self): self.spy.one_arg_method(1) args_checker = assert_that_was_called(self.spy.one_arg_method) self.failUnlessRaises(ArgsDontMatch, args_checker.with_args, 2) def test_was_called_with_same_params_but_no_params_accepted(self): self.spy.hello() args_checker = assert_that_was_called(self.spy.hello) self.failUnlessRaises(ArgsDontMatch, args_checker.with_args, "something") def test_was_called_with_several_parameters(self): self.spy.two_args_method(1, 2) args_checker = assert_that_was_called(self.spy.two_args_method) args_checker.with_args(1, 2) def test_was_called_with_parameters_not_matching(self): self.spy.one_arg_method(1) args_checker = assert_that_was_called(self.spy.one_arg_method) self.failUnlessRaises(ArgsDontMatch, args_checker.with_args, "2") def test_was_called_with_keyed_args_not_matching(self): self.spy.kwarg_method(key_param="foo") args_checker = assert_that_was_called(self.spy.kwarg_method) self.failUnlessRaises(ArgsDontMatch, args_checker.with_args, key_param="bar") def test_was_called_with_keyed_args_matching(self): self.spy.kwarg_method(key_param="foo") assert_that_was_called(self.spy.kwarg_method).with_args( key_param="foo") def test_recorded_call_params_are_displayed(self): self.spy.kwarg_method(key_param="foo") try: assert_that_was_called(self.spy.kwarg_method ).with_args("bar") except ArgsDontMatch, e: self.assertTrue(str(e).find("foo") != -1, str(e)) def test_stub_out_method(self): when(self.spy.one_arg_method).then_return(3) self.assertEquals(3, self.spy.one_arg_method(5)) def test_stub_method_was_called(self): when(self.spy.one_arg_method).then_return(3) self.spy.one_arg_method(5) assert_that_was_called(self.spy.one_arg_method).with_args(5) def test_stub_out_method_returning_a_list(self): when(self.spy.one_arg_method).then_return([1,2,3]) self.assertEquals([1,2,3], self.spy.one_arg_method(5)) def test_stub_method_returning_list_was_called(self): when(self.spy.one_arg_method).then_return([1,2,3]) self.spy.one_arg_method(5) assert_that_was_called(self.spy.one_arg_method).with_args(5) def test_stub_out_method_with_args(self): when(self.spy.one_arg_method).with_args(2).then_return(3) self.assertEquals(3, self.spy.one_arg_method(2)) def test_stub_method_with_args_was_called(self): when(self.spy.one_arg_method).with_args(2).then_return(3) self.spy.one_arg_method(2) assert_that_was_called(self.spy.one_arg_method).with_args(2) def test_stub_out_method_with_args_calls_actual(self): when(self.spy.one_arg_method).with_args(2).then_return(3) self.assertEquals(4, self.spy.one_arg_method(4)) assert_that_was_called(self.spy.one_arg_method).with_args(4) def test_stub_out_method_with_several_inputs(self): when(self.spy.one_arg_method).with_args(2).then_return(3) when(self.spy.one_arg_method).with_args(3).then_return(4) self.assertEquals(3, self.spy.one_arg_method(2)) self.assertEquals(4, self.spy.one_arg_method(3)) def test_recorded_calls_work_on_several_stubs(self): when(self.spy.one_arg_method).with_args(2).then_return(3) when(self.spy.one_arg_method).with_args(3).then_return(4) self.spy.one_arg_method(2) self.spy.one_arg_method(3) assert_that_was_called(self.spy.one_arg_method).with_args(2) assert_that_was_called(self.spy.one_arg_method).with_args(3) def test_matching_stub_definition_is_used(self): when(self.spy.one_arg_method).then_return(1000) when(self.spy.one_arg_method).with_args(2).then_return(3) self.assertEquals(3, self.spy.one_arg_method(2)) self.assertEquals(1000, self.spy.one_arg_method(8)) def test_stub_with_kwargs(self): when(self.spy.kwarg_method).with_args(key_param=2 ).then_return(3) self.assertEquals(3, self.spy.kwarg_method(key_param=2)) self.assertEquals(6, self.spy.kwarg_method(key_param=6)) def test_stub_raising_exception(self): when(self.spy.hello).then_raise(SomeException()) try: self.spy.hello() self.fail("not raised") except SomeException: pass def test_stub_returning_what_receives(self): when(self.spy.method_one).then_return_input() self.assertEquals(20, self.spy.method_one(20)) def test_stub_returning_what_receives_when_no_params(self): when(self.spy.hello).then_return_input() self.failUnlessRaises(ApiMismatch, self.spy.hello) def test_be_able_to_return_objects(self): when(self.spy.one_arg_method).then_return(Collaborator()) collaborator = self.spy.one_arg_method(1) self.assertEquals(1, collaborator.one_arg_method(1)) def test_any_arg_matcher(self): when(self.spy.two_args_method).with_args(1, ANY_ARG).then_return(1000) self.assertEquals(1000, self.spy.two_args_method(1,2)) self.assertEquals(1000, self.spy.two_args_method(1,5)) # TODO: implement this: # def test_any_arg_matcher_with_kwargs(self): # when(self.spy.kwarg_method).with_args(key_param=ANY_ARG).then_return(1000) # # self.assertEquals(1000, self.spy.kwarg_method(key_param=2)) def test_any_arg_matcher_was_called(self): when(self.spy.two_args_method).with_args(1, 2).then_return(1000) self.spy.two_args_method(1,2) assert_that_was_called(self.spy.two_args_method ).with_args(1, ANY_ARG) def test_stub_works_with_alias_method(self): when(self.spy.one_arg_method).with_args(1).then_return(1000) self.spy.alias_method(1) assert_that_was_called(self.spy.one_arg_method ).with_args(1) def test_was_never_called(self): assert_that_method(self.spy.one_arg_method).was_never_called() def test_was_never_called_is_false(self): self.spy.one_arg_method(1) try: assert_that_method(self.spy.one_arg_method).was_never_called() self.fail("it was called indeed!") except UnexpectedBehavior: pass def test_expect_several_times(self): self.spy.one_arg_method(1) try: assert_that_method(self.spy.one_arg_method).was_called().times(2) self.fail("Should have been called 2 times") except UnexpectedBehavior: pass def test_fail_incorrect_times_msg_is_human_readable(self): self.spy.one_arg_method(1) try: assert_that_method(self.spy.one_arg_method).was_called().times(5) self.fail("Should have been called 2 times") except UnexpectedBehavior, e: for arg in e.args: if re.search("5", arg) and re.search("one_arg_method", arg): return self.fail("No enough readable exception message") def test_expect_several_times_matches_exactly(self): self.spy.one_arg_method(1) self.spy.one_arg_method(1) assert_that_method(self.spy.one_arg_method).was_called().times(2) def test_expect_several_times_with_args_definition(self): self.spy.one_arg_method(1) self.spy.one_arg_method(1) assert_that_method(self.spy.one_arg_method).was_called().with_args(1).times(2) def test_expect_several_times_with_incorrect_args(self): self.spy.one_arg_method(1) self.spy.one_arg_method(1) try: assert_that_method(self.spy.one_arg_method).was_called().with_args(2).times(2) self.fail("Must have 1 as an argument") except ArgsDontMatch: pass def test_args_match_but_not_number_of_times(self): self.spy.one_arg_method(1) self.spy.one_arg_method(2) try: assert_that_method(self.spy.one_arg_method ).was_called().with_args(1).times(2) self.fail("Wrong assertion") except UnexpectedBehavior: pass class MethodPoolTests(unittest.TestCase): def setUp(self): self.pool = _MethodPool_() self.method_name = "some_method" self.pool.add_method(self.method_name, input_args=(1,2)) def test_call_args_match(self): self.assertTrue( self.pool.matching_ioparams_by_args( (1,2), {}, self.method_name)) def test_call_args_match_with_any(self): self.assertTrue( self.pool.matching_ioparams_by_args( (1, ANY_ARG), {}, self.method_name)) class SpyTests(unittest.TestCase): def setUp(self): self.spy = spy(Collaborator()) def test_override_original_method(self): self.assertTrue(self.spy.hello() is None) def test_override_original_method_and_is_called(self): self.spy.hello() assert_that_was_called(self.spy.hello) def test_spy_can_work_from_empty_object(self): self.spy = empty_spy() self.assertTrue(self.spy.hello() is None) def test_spy_can_work_from_empty_and_is_called(self): self.spy.hello() assert_that_was_called(self.spy.hello) def test_spy_based_on_object_must_check_api_match(self): try: self.spy.hello("unexpected argument") self.fail('Expection should raise: Actual objet does not accept parameters') except ApiMismatch: pass def test_check_api_match_with_kwargs(self): self.assertTrue(self.spy.mixed_method(1, key_param=2) is None) def test_check_api_match_with_kwargs_not_used(self): self.assertTrue(self.spy.mixed_method(1) is None) def test_check_api_match_with_kwargs_not_matching(self): try: self.spy.mixed_method(1,2,3) self.fail('Api mismatch not detected') except ApiMismatch: pass def test_match_call_with_unicode_and_non_ascii_chars(self): non_ascii = u'España' self.spy.one_arg_method(non_ascii) assert_that_was_called(self.spy.one_arg_method).with_args( non_ascii) def test_stub_methods_can_be_handled_separately(self): when(self.spy.one_arg_method).with_args(1).then_return(1000) when(self.spy.two_args_method).with_args(5,5).then_return(2000) handle1 = self.spy.one_arg_method handle2 = self.spy.two_args_method self.assertEquals(1000, handle1(1)) self.assertEquals(2000, handle2(5,5)) assert_that_was_called(handle1).with_args(1) assert_that_was_called(handle2).with_args(5,5) def test_assert_was_called_with_method_not_in_the_api(self): self.failUnlessRaises(ApiMismatch, assert_that_was_called, self.spy.unexisting_method) def test_do_not_call_callable_object_if_wasnt_generated_by_the_framework(self): class CallableObj(): just_testing = True def __call__(self, *args, **kwargs): raise Exception('should not happen') obj = CallableObj() when(self.spy.one_arg_method).then_return(obj) self.assertEquals(obj, self.spy.one_arg_method(1), "Wrong returned object") class MockTests(unittest.TestCase): def setUp(self): self.mock = mock(Collaborator()) def test_fail_on_unexpected_call(self): try: self.mock.hello() self.fail('UnexpectedBehavior should be raised') except UnexpectedBehavior: pass def test_fail_on_unexpected_call_msg_is_human_readable(self): try: self.mock.hello() except UnexpectedBehavior, e: for arg in e.args: if re.search("No one", arg): return self.fail("No enough readable exception message") def test_define_expectation_and_call_method(self): expect_call(self.mock.hello) self.assertTrue(self.mock.hello() is None) def test_define_several_expectatiosn(self): expect_call(self.mock.hello) expect_call(self.mock.one_arg_method) self.assertTrue(self.mock.hello() is None) self.assertTrue(self.mock.one_arg_method(1) is None) def test_define_expectation_args(self): expect_call(self.mock.one_arg_method).with_args(1) self.assertTrue(self.mock.one_arg_method(1) is None) def test_define_expectation_args_and_fail(self): expect_call(self.mock.one_arg_method).with_args(1) try: self.mock.one_arg_method(2) self.fail('Unexpected call') except UnexpectedBehavior: pass def test_several_expectations_with_args(self): expect_call(self.mock.one_arg_method).with_args(1) expect_call(self.mock.two_args_method).with_args(2,3) self.assertTrue(self.mock.one_arg_method(1) is None) self.assertTrue(self.mock.two_args_method(2,3) is None) def test_expect_call_returning_value(self): expect_call(self.mock.one_arg_method).with_args(1).returning(1000) self.assertEquals(1000, self.mock.one_arg_method(1)) def test_assert_expectations_are_satisfied(self): expect_call(self.mock.hello) try: self.mock.assert_that_is_satisfied() self.fail('Not satisfied!') except UnexpectedBehavior: pass def test_assert_expectations_alternative(self): expect_call(self.mock.hello) try: self.mock.assert_expectations() self.fail('Not satisfied') except UnexpectedBehavior: pass def test_assert_satisfied_when_it_really_is(self): expect_call(self.mock.hello) self.mock.hello() self.mock.assert_that_is_satisfied() def test_number_of_calls_matter(self): expect_call(self.mock.hello) self.mock.hello() self.mock.hello() self.failUnlessRaises(UnexpectedBehavior, self.mock.assert_that_is_satisfied) def test_using_when_or_expect_call_without_double(self): self.failUnlessRaises(WrongApiUsage, expect_call, Collaborator()) def test_expectations_on_synonyms(self): expect_call(self.mock.one_arg_method) self.mock.alias_method(1) self.mock.assert_that_is_satisfied() def test_several_expectations_with_different_args(self): expect_call(self.mock.one_arg_method).with_args(1) expect_call(self.mock.one_arg_method).with_args(2) self.mock.one_arg_method(1) self.mock.one_arg_method(1) self.failUnlessRaises(UnexpectedBehavior, self.mock.assert_that_is_satisfied) def test_expect_several_times(self): expect_call(self.mock.one_arg_method).with_args(1).times(2) self.mock.one_arg_method(1) self.failUnlessRaises(UnexpectedBehavior, self.mock.assert_that_is_satisfied) def test_expect_several_times_matches_exactly(self): expect_call(self.mock.one_arg_method).with_args(1).times(2) self.mock.one_arg_method(1) self.mock.one_arg_method(1) self.mock.assert_that_is_satisfied() def test_expect_several_times_without_args_definition(self): expect_call(self.mock.one_arg_method).times(2) self.mock.one_arg_method(1) self.mock.one_arg_method(1) self.mock.assert_that_is_satisfied() def test_defend_agains_less_than_2_times(self): try: expect_call(self.mock.one_arg_method).times(1) self.fail('times cant be less than 2') except WrongApiUsage: pass def test_times_and_return_value(self): expect_call(self.mock.one_arg_method).returning(1000).times(2) self.assertEquals(1000, self.mock.one_arg_method(1)) self.assertEquals(1000, self.mock.one_arg_method(1)) self.mock.assert_that_is_satisfied() def test_times_and_return_value_and_input_args(self): expect_call(self.mock.one_arg_method).with_args(10).returning(1000).times(2) self.assertEquals(1000, self.mock.one_arg_method(10)) self.assertEquals(1000, self.mock.one_arg_method(10)) self.mock.assert_that_is_satisfied() class MockFromEmptyObjectTests(unittest.TestCase): def setUp(self): self.mock = empty_mock() def test_mock_can_work_from_empty_object(self): expect_call(self.mock.hello) self.mock.hello() self.mock.assert_that_is_satisfied() def test_several_expectations_in_empty_mock(self): expect_call(self.mock.hello) expect_call(self.mock.one_arg_method).with_args(1) self.mock.hello() self.mock.one_arg_method(1) self.mock.assert_that_is_satisfied() def test_several_expectations_with_args_in_empty_mock(self): expect_call(self.mock.one_arg_method).with_args(1) expect_call(self.mock.one_arg_method).with_args(2) self.assertTrue(self.mock.one_arg_method(1) is None) self.assertTrue(self.mock.one_arg_method(2) is None) self.mock.assert_that_is_satisfied() class StubMethodsTests(unittest.TestCase): def setUp(self): self.collaborator = Collaborator() def test_method_returning_value(self): self.collaborator.hello = method_returning("bye") self.assertEquals("bye", self.collaborator.hello()) def test_method_args_returning_value(self): self.collaborator.one_arg_method = method_returning("bye") self.assertEquals("bye", self.collaborator.one_arg_method(1)) def test_method_raising_exception(self): self.collaborator.hello = method_raising(SomeException()) try: self.collaborator.hello() self.fail("exception not raised") except SomeException: pass class MatchersTests(unittest.TestCase): def setUp(self): self.spy = spy(Collaborator()) def test_str_cotaining_with_exact_match(self): when(self.spy.one_arg_method).with_args( str_containing("abc")).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method("abc")) def test_str_containing_with_substr(self): when(self.spy.one_arg_method).with_args( str_containing("abc")).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method("XabcX")) def test_str_containing_with_substr_unicode(self): when(self.spy.one_arg_method).with_args( str_containing("abc")).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method(u"XabcñX")) def test_str_containing_but_matcher_not_used(self): when(self.spy.one_arg_method).with_args( "abc").then_return(1000) self.assertNotEquals(1000, self.spy.one_arg_method("XabcX")) def test_was_called_and_substr_matcher(self): self.spy.one_arg_method("XabcX") assert_that_was_called(self.spy.one_arg_method).with_args( str_containing("abc")) def test_str_not_containing(self): when(self.spy.one_arg_method).with_args( str_not_containing("abc")).then_return(1000) self.assertNotEquals(1000, self.spy.one_arg_method("abc")) def test_str_not_containing_stubs_anything_else(self): when(self.spy.one_arg_method).with_args( str_not_containing("abc")).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method("xxx")) def test_str_not_containing_was_called(self): self.spy.one_arg_method("abc") assert_that_was_called(self.spy.one_arg_method).with_args( str_not_containing("xxx")) def test_several_matchers(self): when(self.spy.two_args_method).with_args( str_containing("abc"), str_containing("xxx")).then_return(1000) self.assertNotEquals(1000, self.spy.two_args_method("abc", "yyy")) def test_str_length_matcher(self): when(self.spy.one_arg_method).with_args( str_length(5)).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method("abcde")) def test_matchers_when_passed_arg_is_none(self): when(self.spy.one_arg_method).with_args( str_length(5)).then_return(1000) self.assertTrue(self.spy.one_arg_method(None) is None) def test_compare_objects_is_not_possible_without_eq_operator(self): class SomeObject(): field1 = field2 = None obj = SomeObject() obj2 = SomeObject() self.spy.one_arg_method(obj) try: assert_that_method(self.spy.one_arg_method).was_called().with_args(obj2) self.fail('they should not match') except ArgsDontMatch: pass def test_if_doesnt_match_message_is_human_redable(self): self.spy.one_arg_method("XabcX") try: assert_that_was_called(self.spy.one_arg_method).with_args( str_containing("xxx")) except ArgsDontMatch, e: self.assertTrue("xxx" in str(e.args), str(e.args)) self.assertTrue("string containing" in str(e.args)) def test_obj_with_field_matcher(self): obj = Collaborator() obj.id = 20 self.spy.one_arg_method(obj) assert_that_method(self.spy.one_arg_method ).was_called().with_args(obj_with_fields({'id': 20})) def test_obj_with_several_fields_matcher(self): obj = Collaborator() obj.id = 21 self.spy.one_arg_method(obj) try: assert_that_method(self.spy.one_arg_method ).was_called().with_args(obj_with_fields({'id': 20, 'test_field': 'OK'})) self.fail('Wrong assertion, id field is different') except ArgsDontMatch, e: pass def test_obj_with_field_defends_agains_wrong_usage(self): self.spy.one_arg_method(Collaborator()) try: assert_that_method(self.spy.one_arg_method ).was_called().with_args(obj_with_fields('id = 20')) self.fail('Wrong assertion, argument should be a dictionary') except WrongApiUsage: pass class CustomMatchersTest(unittest.TestCase): def setUp(self): self.spy = spy(Collaborator()) def test_use_custom_matcher(self): class CustomMatcher(PyDoublesMatcher): matcher_name = "test matcher" def __init__(self, arg): self.defined_arg = arg def matches(self, item): return True when(self.spy.one_arg_method).with_args( CustomMatcher('zzz')).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method('xx')) def test_custom_matcher_do_not_follow_convention(self): class CustomMatcher(PyDoublesMatcher): def matches(self, item): return False self.spy.one_arg_method(1) try: assert_that_was_called(self.spy.one_arg_method).with_args( CustomMatcher()) self.fail('args dont match!') except ArgsDontMatch: pass if __name__ == "__main__": print "Use nosetest to run this tests: nosetest unit.py"pyDoubles-1.4/pyDoublesTests/pyTDDmon_tmp.py0000644000175000017500000000035011575216027021747 0ustar carlosblecarlosbleimport unittest suite = unittest.TestSuite() load_module_tests = unittest.defaultTestLoader.loadTestsFromModule import unit suite.addTests(load_module_tests(unit)) if __name__ == '__main__': unittest.TextTestRunner().run(suite) pyDoubles-1.4/pyDoublesTests/hamcrest_integration.py0000644000175000017500000000567711605565524023626 0ustar carlosblecarlosble# -*- coding: utf-8 -*- """ Authors: Carlos Ble (www.carlosble.com) Ruben Bernardez (www.rubenbp.com) www.iExpertos.com License: Apache 2 (http://www.apache.org/licenses/LICENSE-2.0.html) Project home: https://bitbucket.org/carlosble/pydoubles """ import unittest from hamcrest.core.core import * from hamcrest.library.collection.isdict_containing import has_entry from hamcrest.library.object.haslength import has_length from hamcrest.library.text.isequal_ignoring_case import equal_to_ignoring_case from hamcrest.library.text.stringstartswith import starts_with from pyDoubles.framework import * from pyDoublesTests.unit import Collaborator class HamcrestIntegrationTest(unittest.TestCase): def setUp(self): self.spy = spy(Collaborator()) def test_use_in_stub_method(self): when(self.spy.one_arg_method).with_args( starts_with('tt')).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method('ttxe')) def test_use_in_spy_call(self): self.spy.one_arg_method('ttxe') assert_that_was_called( self.spy.one_arg_method).with_args(starts_with('tt')) def test_is_matcher(self): class Customer: pass customer = Customer() when(self.spy.one_arg_method).with_args( is_(customer)).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method(customer)) def test_instance_of_matcher(self): when(self.spy.one_arg_method).with_args( instance_of(int)).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method(5)) def test_all_of_matcher(self): text = 'hello' when(self.spy.one_arg_method).with_args( all_of(starts_with('h'),equal_to(text))).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method(text)) def test_has_length_matcher(self): list = [10,20,30] when(self.spy.one_arg_method).with_args( has_length(3)).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method(list)) def test_has_entry_matcher(self): list = {'one':1, 'two':2} when(self.spy.one_arg_method).with_args( has_entry(equal_to('two'), 2)).then_return(1000) self.assertEquals(1000, self.spy.one_arg_method(list)) def test_equal_to_ignoring_case_matcher(self): self.spy.one_arg_method('hello') assert_that_was_called(self.spy.one_arg_method).with_args( equal_to_ignoring_case('HEllO')) def test_matcher_error_is_human_readable(self): self.spy.one_arg_method('xe') try: assert_that_method(self.spy.one_arg_method).was_called().with_args( starts_with('tt')) except ArgsDontMatch, e: self.assertTrue("tt" in str(e.args)) self.assertTrue("string starting" in str(e.args)) if __name__ == "__main__": print "Use nosetest to run this tests: nosetest hamcrest_integration.py"pyDoubles-1.4/setup.py0000644000175000017500000000061211651313573015540 0ustar carlosblecarlosblefrom distutils.core import setup setup( name='pyDoubles', version='1.4', author='Carlos Ble', author_email='carlos@iexpertos.com', packages=['pyDoubles', 'pyDoublesTests'], scripts=[], url='https://bitbucket.org/carlosble/pydoubles', license='LICENSE.txt', description='Test doubles framework for Python', long_description=open('README.txt').read(), ) pyDoubles-1.4/CHANGES.txt0000644000175000017500000000070311651314364015637 0ustar carlosblecarlosblev1.4, 24 oct 2011 -- http://www.carlosble.com/2011/10/pydoubles-14-releasedpydoubles-14-released/ v1.3, 19 jul 2011 -- http://www.carlosble.com/2011/07/pydoubles-13-releasedpydoubles-13-released/ v1.2 , 8 jul 2011 -- http://www.rubenbernardez.com/blog/2011/07/pydoubles-v1-2-released-hamcrest-compatibility/ v1.1 , 1 jul 2011 -- http://www.carlosble.com/2011/07/pydoubles-11-releasedpydoubles-11-released/ v1.0beta, 24 may 2011 -- Initial release.