From b4c8b4229d8f95b499466af7bbd573e29bbf8a49 Mon Sep 17 00:00:00 2001 From: Ryan Tomac Date: Fri, 30 Sep 2011 16:16:48 -0700 Subject: [PATCH] Add mockito dependency --- test/lib/mockito/__init__.py | 27 +++++ test/lib/mockito/inorder.py | 15 +++ test/lib/mockito/invocation.py | 175 ++++++++++++++++++++++++++++++ test/lib/mockito/matchers.py | 58 ++++++++++ test/lib/mockito/mock_registry.py | 19 ++++ test/lib/mockito/mocking.py | 106 ++++++++++++++++++ test/lib/mockito/mockito.py | 93 ++++++++++++++++ test/lib/mockito/spying.py | 41 +++++++ test/lib/mockito/verification.py | 81 ++++++++++++++ test/run_unit_tests.py | 7 +- test/unit/test_browsercache.py | 37 ++++--- 11 files changed, 640 insertions(+), 19 deletions(-) create mode 100644 test/lib/mockito/__init__.py create mode 100644 test/lib/mockito/inorder.py create mode 100644 test/lib/mockito/invocation.py create mode 100644 test/lib/mockito/matchers.py create mode 100644 test/lib/mockito/mock_registry.py create mode 100644 test/lib/mockito/mocking.py create mode 100644 test/lib/mockito/mockito.py create mode 100644 test/lib/mockito/spying.py create mode 100644 test/lib/mockito/verification.py diff --git a/test/lib/mockito/__init__.py b/test/lib/mockito/__init__.py new file mode 100644 index 000000000..3a3c29bce --- /dev/null +++ b/test/lib/mockito/__init__.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# coding: utf-8 + +'''Mockito is a Test Spy framework.''' + +__copyright__ = "Copyright 2008-2010, Mockito Contributors" +__license__ = "MIT" +__maintainer__ = "Mockito Maintainers" +__email__ = "mockito-python@googlegroups.com" + +from mockito import mock, verify, verifyNoMoreInteractions, verifyZeroInteractions, when, unstub, ArgumentError +import inorder +from spying import spy +from verification import VerificationError + +# Imports for compatibility +from mocking import Mock +from matchers import any, contains, times # use package import (``from mockito.matchers import any, contains``) instead of ``from mockito import any, contains`` +from verification import never + +__all__ = ['mock', 'spy', 'verify', 'verifyNoMoreInteractions', 'verifyZeroInteractions', 'inorder', 'when', 'unstub', 'VerificationError', 'ArgumentError', + 'Mock', # deprecated + 'any', # compatibility + 'contains', # compatibility + 'never', # compatibility + 'times' # deprecated + ] diff --git a/test/lib/mockito/inorder.py b/test/lib/mockito/inorder.py new file mode 100644 index 000000000..8c9d801d0 --- /dev/null +++ b/test/lib/mockito/inorder.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# coding: utf-8 + +from mockito import verify as verify_main + +__author__ = "Serhiy Oplakanets " +__copyright__ = "Copyright 2008-2010, Mockito Contributors" +__license__ = "MIT" +__maintainer__ = "Mockito Maintainers" +__email__ = "mockito-python@googlegroups.com" + +def verify(object, *args, **kwargs): + kwargs['inorder'] = True + return verify_main(object, *args, **kwargs) + diff --git a/test/lib/mockito/invocation.py b/test/lib/mockito/invocation.py new file mode 100644 index 000000000..067881b99 --- /dev/null +++ b/test/lib/mockito/invocation.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python +# coding: utf-8 + +import matchers + +__copyright__ = "Copyright 2008-2010, Mockito Contributors" +__license__ = "MIT" +__maintainer__ = "Mockito Maintainers" +__email__ = "mockito-python@googlegroups.com" + +class InvocationError(AssertionError): + pass + +class Invocation(object): + def __init__(self, mock, method_name): + self.method_name = method_name + self.mock = mock + self.verified = False + self.verified_inorder = False + self.params = () + self.named_params = {} + self.answers = [] + self.strict = mock.strict + + def _remember_params(self, params, named_params): + self.params = params + self.named_params = named_params + + def __repr__(self): + return self.method_name + "(" + ", ".join([repr(p) for p in self.params]) + ")" + + def answer_first(self): + return self.answers[0].answer() + +class MatchingInvocation(Invocation): + @staticmethod + def compare(p1, p2): + if isinstance(p1, matchers.Matcher): + if not p1.matches(p2): return False + elif p1 != p2: return False + return True + + def matches(self, invocation): + if self.method_name != invocation.method_name: + return False + if len(self.params) != len(invocation.params): + return False + if len(self.named_params) != len(invocation.named_params): + return False + if self.named_params.keys() != invocation.named_params.keys(): + return False + + for x, p1 in enumerate(self.params): + if not self.compare(p1, invocation.params[x]): + return False + + for x, p1 in self.named_params.iteritems(): + if not self.compare(p1, invocation.named_params[x]): + return False + + return True + +class RememberedInvocation(Invocation): + def __call__(self, *params, **named_params): + self._remember_params(params, named_params) + self.mock.remember(self) + + for matching_invocation in self.mock.stubbed_invocations: + if matching_invocation.matches(self): + return matching_invocation.answer_first() + + return None + +class RememberedProxyInvocation(Invocation): + '''Remeber params and proxy to method of original object. + + Calls method on original object and returns it's return value. + ''' + def __call__(self, *params, **named_params): + self._remember_params(params, named_params) + self.mock.remember(self) + obj = self.mock.original_object + try: + method = getattr(obj, self.method_name) + except AttributeError: + raise AttributeError("You tried to call method '%s' which '%s' instance does not have." % (self.method_name, obj.__class__.__name__)) + return method(*params, **named_params) + +class VerifiableInvocation(MatchingInvocation): + def __call__(self, *params, **named_params): + self._remember_params(params, named_params) + matched_invocations = [] + for invocation in self.mock.invocations: + if self.matches(invocation): + matched_invocations.append(invocation) + + verification = self.mock.pull_verification() + verification.verify(self, len(matched_invocations)) + + for invocation in matched_invocations: + invocation.verified = True + +class StubbedInvocation(MatchingInvocation): + def __init__(self, *params): + super(StubbedInvocation, self).__init__(*params) + if self.mock.strict: + self.ensure_mocked_object_has_method(self.method_name) + + def ensure_mocked_object_has_method(self, method_name): + if not self.mock.has_method(method_name): + raise InvocationError("You tried to stub a method '%s' the object (%s) doesn't have." + % (method_name, self.mock.mocked_obj)) + + + def __call__(self, *params, **named_params): + self._remember_params(params, named_params) + return AnswerSelector(self) + + def stub_with(self, answer): + self.answers.append(answer) + self.mock.stub(self.method_name) + self.mock.finish_stubbing(self) + +class AnswerSelector(object): + def __init__(self, invocation): + self.invocation = invocation + self.answer = None + + def thenReturn(self, *return_values): + for return_value in return_values: + self.__then(Return(return_value)) + return self + + def thenRaise(self, *exceptions): + for exception in exceptions: + self.__then(Raise(exception)) + return self + + def __then(self, answer): + if not self.answer: + self.answer = CompositeAnswer(answer) + self.invocation.stub_with(self.answer) + else: + self.answer.add(answer) + + return self + +class CompositeAnswer(object): + def __init__(self, answer): + self.answers = [answer] + + def add(self, answer): + self.answers.insert(0, answer) + + def answer(self): + if len(self.answers) > 1: + a = self.answers.pop() + else: + a = self.answers[0] + + return a.answer() + +class Raise(object): + def __init__(self, exception): + self.exception = exception + + def answer(self): + raise self.exception + +class Return(object): + def __init__(self, return_value): + self.return_value = return_value + + def answer(self): + return self.return_value diff --git a/test/lib/mockito/matchers.py b/test/lib/mockito/matchers.py new file mode 100644 index 000000000..fcfb3d313 --- /dev/null +++ b/test/lib/mockito/matchers.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# coding: utf-8 + +'''Matchers for stubbing and verifications. + +Common matchers for use in stubbing and verifications. +''' + +__copyright__ = "Copyright 2008-2010, Mockito Contributors" +__license__ = "MIT" +__maintainer__ = "Mockito Maintainers" +__email__ = "mockito-python@googlegroups.com" + +__all__ = ['any', 'contains', 'times'] + +class Matcher: + def matches(self, arg): + pass + +class Any(Matcher): + def __init__(self, wanted_type=None): + self.wanted_type = wanted_type + + def matches(self, arg): + if self.wanted_type: + return isinstance(arg, self.wanted_type) + else: + return True + + def __repr__(self): + return "" % self.wanted_type + +class Contains(Matcher): + def __init__(self, sub): + self.sub = sub + + def matches(self, arg): + if not hasattr(arg, 'find'): + return + return self.sub and len(self.sub) > 0 and arg.find(self.sub) > -1 + + def __repr__(self): + return "" % self.sub + + +def any(wanted_type=None): + """Matches any() argument OR any(SomeClass) argument + Examples: + when(mock).foo(any()).thenReturn(1) + verify(mock).foo(any(int)) + """ + return Any(wanted_type) + +def contains(sub): + return Contains(sub) + +def times(count): + return count diff --git a/test/lib/mockito/mock_registry.py b/test/lib/mockito/mock_registry.py new file mode 100644 index 000000000..c7a0208f6 --- /dev/null +++ b/test/lib/mockito/mock_registry.py @@ -0,0 +1,19 @@ +class MockRegistry: + """Registers mock()s, ensures that we only have one mock() per mocked_obj, and + iterates over them to unstub each stubbed method. """ + + def __init__(self): + self.mocks = {} + + def register(self, mock): + self.mocks[mock.mocked_obj] = mock + + def mock_for(self, cls): + return self.mocks.get(cls, None) + + def unstub_all(self): + for mock in self.mocks.itervalues(): + mock.unstub() + self.mocks.clear() + +mock_registry = MockRegistry() \ No newline at end of file diff --git a/test/lib/mockito/mocking.py b/test/lib/mockito/mocking.py new file mode 100644 index 000000000..9539ddd62 --- /dev/null +++ b/test/lib/mockito/mocking.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# coding: utf-8 + +import inspect +import invocation +from mock_registry import mock_registry +import warnings + +__copyright__ = "Copyright 2008-2010, Mockito Contributors" +__license__ = "MIT" +__maintainer__ = "Mockito Maintainers" +__email__ = "mockito-python@googlegroups.com" + +__all__ = ['mock', 'Mock'] + +class _Dummy(object): pass + +class TestDouble(object): pass + +class mock(TestDouble): + def __init__(self, mocked_obj=None, strict=True): + self.invocations = [] + self.stubbed_invocations = [] + self.original_methods = [] + self.stubbing = None + self.verification = None + if mocked_obj is None: + mocked_obj = _Dummy() + strict = False + self.mocked_obj = mocked_obj + self.strict = strict + self.stubbing_real_object = False + + mock_registry.register(self) + + def __getattr__(self, method_name): + if self.stubbing is not None: + return invocation.StubbedInvocation(self, method_name) + + if self.verification is not None: + return invocation.VerifiableInvocation(self, method_name) + + return invocation.RememberedInvocation(self, method_name) + + def remember(self, invocation): + self.invocations.insert(0, invocation) + + def finish_stubbing(self, stubbed_invocation): + self.stubbed_invocations.insert(0, stubbed_invocation) + self.stubbing = None + + def expect_stubbing(self): + self.stubbing = True + + def pull_verification(self): + v = self.verification + self.verification = None + return v + + def has_method(self, method_name): + return hasattr(self.mocked_obj, method_name) + + def get_method(self, method_name): + return self.mocked_obj.__dict__.get(method_name) + + def set_method(self, method_name, new_method): + setattr(self.mocked_obj, method_name, new_method) + + def replace_method(self, method_name, original_method): + + def new_mocked_method(*args, **kwargs): + # we throw away the first argument, if it's either self or cls + if inspect.isclass(self.mocked_obj) and not isinstance(original_method, staticmethod): + args = args[1:] + call = self.__getattr__(method_name) # that is: invocation.RememberedInvocation(self, method_name) + return call(*args, **kwargs) + + if isinstance(original_method, staticmethod): + new_mocked_method = staticmethod(new_mocked_method) + elif isinstance(original_method, classmethod): + new_mocked_method = classmethod(new_mocked_method) + + self.set_method(method_name, new_mocked_method) + + def stub(self, method_name): + original_method = self.get_method(method_name) + original = (method_name, original_method) + self.original_methods.append(original) + + # If we're trying to stub real object(not a generated mock), then we should patch object to use our mock method. + # TODO: Polymorphism was invented long time ago. Refactor this. + if self.stubbing_real_object: + self.replace_method(method_name, original_method) + + def unstub(self): + while self.original_methods: + method_name, original_method = self.original_methods.pop() + self.set_method(method_name, original_method) + +def Mock(*args, **kwargs): + '''A ``mock``() alias. + + Alias for compatibility. To be removed in version 1.0. + ''' + warnings.warn("\n`Mock()` is deprecated, please use `mock()` (lower 'm') instead.", DeprecationWarning) + return mock(*args, **kwargs) diff --git a/test/lib/mockito/mockito.py b/test/lib/mockito/mockito.py new file mode 100644 index 000000000..30f188a4f --- /dev/null +++ b/test/lib/mockito/mockito.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# coding: utf-8 + +import verification +from mocking import mock, TestDouble +from mock_registry import mock_registry +from verification import VerificationError + +__copyright__ = "Copyright 2008-2010, Mockito Contributors" +__license__ = "MIT" +__maintainer__ = "Mockito Maintainers" +__email__ = "mockito-python@googlegroups.com" + +class ArgumentError(Exception): + pass + +def _multiple_arguments_in_use(*args): + return len(filter(lambda x: x, args)) > 1 + +def _invalid_argument(value): + return (value is not None and value < 1) or value == 0 + +def _invalid_between(between): + if between is not None: + start, end = between + if start > end or start < 0: + return True + return False + +def verify(obj, times=1, atleast=None, atmost=None, between=None, inorder=False): + if times < 0: + raise ArgumentError("""'times' argument has invalid value. + It should be at least 0. You wanted to set it to: %i""" % times) + if _multiple_arguments_in_use(atleast, atmost, between): + raise ArgumentError("""Sure you know what you are doing? + You can set only one of the arguments: 'atleast', 'atmost' or 'between'.""") + if _invalid_argument(atleast): + raise ArgumentError("""'atleast' argument has invalid value. + It should be at least 1. You wanted to set it to: %i""" % atleast) + if _invalid_argument(atmost): + raise ArgumentError("""'atmost' argument has invalid value. + It should be at least 1. You wanted to set it to: %i""" % atmost) + if _invalid_between(between): + raise ArgumentError("""'between' argument has invalid value. + It should consist of positive values with second number not greater than first + e.g. [1, 4] or [0, 3] or [2, 2] + You wanted to set it to: %s""" % between) + + if isinstance(obj, TestDouble): + mocked_object = obj + else: + mocked_object = mock_registry.mock_for(obj) + + if atleast: + mocked_object.verification = verification.AtLeast(atleast) + elif atmost: + mocked_object.verification = verification.AtMost(atmost) + elif between: + mocked_object.verification = verification.Between(*between) + else: + mocked_object.verification = verification.Times(times) + + if inorder: + mocked_object.verification = verification.InOrder(mocked_object.verification) + + return mocked_object + +def when(obj, strict=True): + if isinstance(obj, mock): + theMock = obj + else: + theMock = mock_registry.mock_for(obj) + if theMock is None: + theMock = mock(obj, strict=strict) + # If we call when on something that is not TestDouble that means we're trying to stub real object, + # (class, module etc.). Not to be confused with generating stubs from real classes. + theMock.stubbing_real_object = True + + theMock.expect_stubbing() + return theMock + +def unstub(): + """Unstubs all stubbed methods and functions""" + mock_registry.unstub_all() + +def verifyNoMoreInteractions(*mocks): + for mock in mocks: + for i in mock.invocations: + if not i.verified: + raise VerificationError("\nUnwanted interaction: " + str(i)) + +def verifyZeroInteractions(*mocks): + verifyNoMoreInteractions(*mocks) diff --git a/test/lib/mockito/spying.py b/test/lib/mockito/spying.py new file mode 100644 index 000000000..fcb3ded30 --- /dev/null +++ b/test/lib/mockito/spying.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# coding: utf-8 + +'''Spying on real objects.''' + +from invocation import RememberedProxyInvocation, VerifiableInvocation +from mocking import TestDouble + +__author__ = "Serhiy Oplakanets " +__copyright__ = "Copyright 2009-2010, Mockito Contributors" +__license__ = "MIT" +__maintainer__ = "Mockito Maintainers" +__email__ = "mockito-python@googlegroups.com" + +__all__ = ['spy'] + +def spy(original_object): + return Spy(original_object) + +class Spy(TestDouble): + strict = True # spies always have to check if method exists + + def __init__(self, original_object): + self.original_object = original_object + self.invocations = [] + self.verification = None + + def __getattr__(self, name): + if self.verification: + return VerifiableInvocation(self, name) + else: + return RememberedProxyInvocation(self, name) + + def remember(self, invocation): + self.invocations.insert(0, invocation) + + def pull_verification(self): + v = self.verification + self.verification = None + return v + \ No newline at end of file diff --git a/test/lib/mockito/verification.py b/test/lib/mockito/verification.py new file mode 100644 index 000000000..4acf0b42f --- /dev/null +++ b/test/lib/mockito/verification.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# coding: utf-8 + +__copyright__ = "Copyright 2008-2010, Mockito Contributors" +__license__ = "MIT" +__maintainer__ = "Mockito Maintainers" +__email__ = "mockito-python@googlegroups.com" + +__all__ = ['never', 'VerificationError'] + +class VerificationError(AssertionError): + '''Indicates error during verification of invocations. + + Raised if verification fails. Error message contains the cause. + ''' + pass + +class AtLeast(object): + def __init__(self, wanted_count): + self.wanted_count = wanted_count + + def verify(self, invocation, actual_count): + if actual_count < self.wanted_count: + raise VerificationError("\nWanted at least: %i, actual times: %i" % (self.wanted_count, actual_count)) + +class AtMost(object): + def __init__(self, wanted_count): + self.wanted_count = wanted_count + + def verify(self, invocation, actual_count): + if actual_count > self.wanted_count: + raise VerificationError("\nWanted at most: %i, actual times: %i" % (self.wanted_count, actual_count)) + +class Between(object): + def __init__(self, wanted_from, wanted_to): + self.wanted_from = wanted_from + self.wanted_to = wanted_to + + def verify(self, invocation, actual_count): + if actual_count < self.wanted_from or actual_count > self.wanted_to: + raise VerificationError("\nWanted between: [%i, %i], actual times: %i" % (self.wanted_from, self.wanted_to, actual_count)) + +class Times(object): + def __init__(self, wanted_count): + self.wanted_count = wanted_count + + def verify(self, invocation, actual_count): + if actual_count == self.wanted_count: + return + if actual_count == 0: + raise VerificationError("\nWanted but not invoked: %s" % (invocation)) + else: + if self.wanted_count == 0: + raise VerificationError("\nUnwanted invocation of %s, times: %i" % (invocation, actual_count)) + else: + raise VerificationError("\nWanted times: %i, actual times: %i" % (self.wanted_count, actual_count)) + +class InOrder(object): + ''' + Verifies invocations in order. + + Verifies if invocation was in expected order, and if yes -- degrades to original Verifier (AtLeast, Times, Between, ...). + ''' + + def __init__(self, original_verification): + ''' + @param original_verification: Original verifiaction to degrade to if order of invocation was ok. + ''' + self.original_verification = original_verification + + def verify(self, wanted_invocation, count): + for invocation in reversed(wanted_invocation.mock.invocations): + if not invocation.verified_inorder: + if not wanted_invocation.matches(invocation): + raise VerificationError("\nWanted %s to be invoked, got %s instead" % (wanted_invocation, invocation)) + invocation.verified_inorder = True + break + # proceed with original verification + self.original_verification.verify(wanted_invocation, count) + +never = 0 diff --git a/test/run_unit_tests.py b/test/run_unit_tests.py index 4006350b1..ea2ee6cec 100644 --- a/test/run_unit_tests.py +++ b/test/run_unit_tests.py @@ -8,8 +8,13 @@ def run_unit_tests(): testfile = re.compile("^test_.*\.py$", re.IGNORECASE) basedir = os.path.abspath(os.path.dirname(__file__)) testdir = os.path.join(basedir, 'unit') + path_dirs = [ + testdir, + os.path.join(basedir, 'lib'), + os.path.join(basedir, '..', 'src') + ] - for path in [testdir, os.path.join(basedir, '..', 'src')]: + for path in path_dirs: if path not in sys.path: sys.path.insert(0, path) diff --git a/test/unit/test_browsercache.py b/test/unit/test_browsercache.py index c39900bc2..34f118734 100644 --- a/test/unit/test_browsercache.py +++ b/test/unit/test_browsercache.py @@ -1,8 +1,9 @@ import unittest import os from Selenium2Library.browsercache import BrowserCache +from mockito import * -class MyTests(unittest.TestCase): +class BrowserCacheTests(unittest.TestCase): def test_no_current_message(self): cache = BrowserCache() @@ -10,33 +11,33 @@ def test_no_current_message(self): cache.current.anyMember() self.assertEqual(context.exception.message, "No current browser") + def test_close(self): + cache = BrowserCache() + browser = mock() + cache.register(browser) + + verify(browser, times=0).close() # sanity check + cache.close() + verify(browser, times=1).close() + def test_close_only_called_once(self): cache = BrowserCache() - browser1 = FakeBrowser() - browser2 = FakeBrowser() - browser3 = FakeBrowser() + browser1 = mock() + browser2 = mock() + browser3 = mock() cache.register(browser1) cache.register(browser2) cache.register(browser3) - self.assertIs(cache.current, browser3) # sanity check - cache.close() # called on browser3 - self.assertEquals(browser3.close_count, 1) # ensure close called + cache.close() + verify(browser3, times=1).close() cache.close_all() - self.assertEquals(browser1.close_count, 1) - self.assertEquals(browser2.close_count, 1) - self.assertEquals(browser3.close_count, 1) - -class FakeBrowser: - - def __init__(self): - self.close_count = 0 - - def close(self): - self.close_count = self.close_count + 1 + verify(browser1, times=1).close() + verify(browser2, times=1).close() + verify(browser3, times=1).close() if __name__ == "__main__": unittest.main()