Allow to decorate class with mock

Now it is possible to mock not only a function but also a class:

  class TestClass(object)

    def test_func_a(self, m):
      m.register_uri('GET', 'http://test.com', text='data')
      ...

    def test_func_b(self, m)
      m.register_uri('GET', 'http://test.com', text='data')
      ...

This new behavior mimics behavior of `patch` from `mock` library.
Added docs for this new feature.

Closes-Bug: #1404805
Change-Id: I8303dc4bc682cf12ffe86e7712b5a1c54de83efb
This commit is contained in:
Sebastian Kalinowski 2014-12-18 20:53:02 +01:00
parent 931f2b4989
commit 1774bb11a9
3 changed files with 146 additions and 1 deletions

View File

@ -47,6 +47,37 @@ If the position of the mock is likely to conflict with other arguments you can p
>>> test_kw_function()
'resp'
Class Decorator
===============
Mocker can also be used to decorate a whole class. It works exactly like in case of decorating a normal function.
When used in this way they wrap every test method on the class. The mocker recognise methods that start with *test* as being test methods.
This is the same way that the `unittest.TestLoader` finds test methods by default.
It is possible that you want to use a different prefix for your tests. You can inform the mocker of the different prefix by setting `requests_mock.Mocker.TEST_PREFIX`:
.. doctest::
>>> requests_mock.Mocker.TEST_PREFIX = 'foo'
>>>
>>> @requests_mock.Mocker()
... class Thing(object):
... def foo_one(self, m):
... m.register_uri('GET', 'http://test.com', text='resp')
... return requests.get('http://test.com').text
... def foo_two(self, m):
... m.register_uri('GET', 'http://test.com', text='resp')
... return requests.get('http://test.com').text
...
>>>
>>> Thing().foo_one()
'resp'
>>> Thing().foo_two()
'resp'
This behavior mimics how patchers from `mock` library works.
Methods
=======

View File

@ -123,6 +123,10 @@ class MockerCore(object):
class Mocker(MockerCore):
"""The standard entry point for mock Adapter loading.
"""
#: Defines with what should method name begin to be patched
TEST_PREFIX = 'test'
def __init__(self, **kwargs):
"""Create a new mocker adapter.
@ -141,7 +145,26 @@ class Mocker(MockerCore):
def __exit__(self, type, value, traceback):
self.stop()
def __call__(self, func):
def __call__(self, obj):
if isinstance(obj, type):
return self.decorate_class(obj)
return self.decorate_callable(obj)
def copy(self):
"""Returns an exact copy of current mock
"""
m = Mocker(
kw=self._kw,
real_http=self._real_http
)
return m
def decorate_callable(self, func):
"""Decorates a callable
:param callable func: callable to decorate
"""
@functools.wraps(func)
def inner(*args, **kwargs):
with self as m:
@ -155,5 +178,25 @@ class Mocker(MockerCore):
return inner
def decorate_class(self, klass):
"""Decorates methods in a class with request_mock
Method will be decorated only if it name begins with `TEST_PREFIX`
:param object klass: class which methods will be decorated
"""
for attr_name in dir(klass):
if not attr_name.startswith(self.TEST_PREFIX):
continue
attr = getattr(klass, attr_name)
if not hasattr(attr, '__call__'):
continue
m = self.copy()
setattr(klass, attr_name, m(attr))
return klass
mock = Mocker

View File

@ -88,6 +88,70 @@ class MockerTests(base.TestCase):
inner()
self.assertMockStopped()
def test_with_class_decorator(self):
outer = self
@requests_mock.mock()
class Decorated(object):
def test_will_be_decorated(self, m):
outer.assertMockStarted()
outer._do_test(m)
def will_not_be_decorated(self):
outer.assertMockStopped()
decorated_class = Decorated()
self.assertMockStopped()
decorated_class.test_will_be_decorated()
self.assertMockStopped()
decorated_class.will_not_be_decorated()
self.assertMockStopped()
def test_with_class_decorator_and_custom_kw(self):
outer = self
@requests_mock.mock(kw='custom_m')
class Decorated(object):
def test_will_be_decorated(self, **kwargs):
outer.assertMockStarted()
outer._do_test(kwargs['custom_m'])
def will_not_be_decorated(self):
outer.assertMockStopped()
decorated_class = Decorated()
self.assertMockStopped()
decorated_class.test_will_be_decorated()
self.assertMockStopped()
decorated_class.will_not_be_decorated()
self.assertMockStopped()
@mock.patch.object(requests_mock.mock, 'TEST_PREFIX', 'foo')
def test_with_class_decorator_and_custom_test_prefix(self):
outer = self
@requests_mock.mock()
class Decorated(object):
def foo_will_be_decorated(self, m):
outer.assertMockStarted()
outer._do_test(m)
def will_not_be_decorated(self):
outer.assertMockStopped()
decorated_class = Decorated()
self.assertMockStopped()
decorated_class.foo_will_be_decorated()
self.assertMockStopped()
decorated_class.will_not_be_decorated()
self.assertMockStopped()
@requests_mock.mock()
def test_query_string(self, m):
url = 'http://test.url/path'
@ -114,6 +178,13 @@ class MockerTests(base.TestCase):
self.assertEqual(m.request_history, matcher.request_history)
self.assertIs(m.last_request, matcher.last_request)
def test_copy(self):
mocker = requests_mock.mock(kw='foo', real_http=True)
copy_of_mocker = mocker.copy()
self.assertIsNot(copy_of_mocker, mocker)
self.assertEqual(copy_of_mocker._kw, mocker._kw)
self.assertEqual(copy_of_mocker._real_http, mocker._real_http)
class MockerHttpMethodsTests(base.TestCase):