Create the Mocker

A standard means of mock loading the adapter. Because we have this move
fixtures stuff into contrib.

Add some documentation for all this.
This commit is contained in:
Jamie Lennox 2014-06-23 16:00:49 +10:00
parent d826ba6f82
commit 833ee180c3
13 changed files with 323 additions and 77 deletions

11
docs/contrib.rst Normal file

@ -0,0 +1,11 @@
==================
Additional Loading
==================
Common additional loading mechanism are supported in the :py:mod:`requests_mock.contrib` module.
These modules may require dependencies outside of what is provided by `requests_mock` and so must be provided by the including application.
.. toctree::
fixture

@ -1,6 +1,6 @@
=======
Fixture
=======
========
Fixtures
========
`Fixtures`_ provide a way to create reusable state and helper methods in test cases.
@ -17,7 +17,7 @@ The fixture provides the same interfaces as the adapter.
.. code:: python
>>> import requests
>>> from requests_mock import fixture
>>> from requests_mock.contrib import fixture
>>> import testtools
>>> class MyTestCase(testtools.TestCase):

@ -1,17 +1,17 @@
Welcome to requests-mock's documentation!
======================================
=========================================
Contents:
.. toctree::
:maxdepth: 3
:maxdepth: 2
overview
adapter
matching
loading
mocker
contrib
Indices and tables
==================

@ -1,9 +0,0 @@
===============
Adapter Loading
===============
Loading can be accomplished in a number of ways:
.. toctree::
fixture

65
docs/mocker.rst Normal file

@ -0,0 +1,65 @@
==============
Mocker Loading
==============
Loading of the Adapter is handled by the :py:class:`requests_mock.Mocker` class, which provides two ways to load an adapter.
Context Manager
===============
The Mocker object can work as a context manager.
.. code:: python
>>> import requests
>>> import requests_mock
>>> with requests_mock.Mocker() as m:
... m.register_uri('GET', 'http://test.com', text='resp')
... requests.get('http://test.com').text
...
'resp'
Decorator
=========
Mocker can also be used as a decorator. The created object will then be passed as the last positional argument.
.. code:: python
>>> @requests_mock.Mocker()
... def test_function(m):
... m.register_uri('GET', 'http://test.com', text='resp')
... return requests.get('http://test.com').text
...
>>> test_function()
'resp'
If the position of the mock is likely to conflict with other arguments you can pass the `kw` argument to the Mocker to have the mocker object passed as that keyword argument instead.
.. code:: python
>>> @requests_mock.Mocker(kw='mock')
... def test_function(**kwargs):
... kwargs['mock'].register_uri('GET', 'http://test.com', test='resp')
... return requests.get('http://test.com').text
...
>>> test_function()
'resp'
Real HTTP Requests
==================
The Mocker object takes the following parameters:
:real_http (bool): If True then any requests that are not handled by the mocking adapter will be forwarded to the real server. Defaults to False.
.. code:: python
>>> with requests_mock.Mocker(real_http=True) as m:
... m.register_uri('GET', 'http://test.com', text='resp')
... print requests.get('http:/test.com').text
... print requests.get('http://www.google.com').status_code
...
'resp'
200

@ -12,6 +12,13 @@
from requests_mock.adapter import Adapter, ANY
from requests_mock.exceptions import MockException, NoMockAddress
from requests_mock.mocker import Mocker, MockerCore
__all__ = ['Adapter', 'ANY', 'MockException', 'NoMockAddress']
__all__ = ['Adapter',
'ANY',
'Mocker',
'MockerCore',
'MockException',
'NoMockAddress'
]

@ -0,0 +1,27 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
from requests_mock import mocker
class Fixture(fixtures.Fixture, mocker.Mocker):
def __init__(self, **kwargs):
fixtures.Fixture.__init__(self)
mocker.Mocker.__init__(self, **kwargs)
def setUp(self):
super(Fixture, self).setUp()
self.start()
self.addCleanup(self.stop)

@ -1,55 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import fixtures
import requests
from requests_mock import adapter
class Fixture(fixtures.Fixture):
PROXY_FUNCS = set(['last_request',
'register_uri',
'request_history'])
def __init__(self):
super(Fixture, self).__init__()
self.adapter = adapter.Adapter()
self._original_get_adapter = None
def _cleanup(self):
if not self._original_get_adapter:
return
requests.Session.get_adapter = self._original_get_adapter
self._original_get_adapter = None
def _get_adapter(self, url):
return self.adapter
def setUp(self):
super(Fixture, self).setUp()
self._original_get_adapter = requests.Session.get_adapter
requests.Session.get_adapter = self._get_adapter
self.addCleanup(self._cleanup)
def __getattr__(self, name):
if name in self.PROXY_FUNCS:
try:
return getattr(self.adapter, name)
except AttributeError:
pass
raise AttributeError(name)

121
requests_mock/mocker.py Normal file

@ -0,0 +1,121 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import functools
import requests
from requests_mock import adapter
from requests_mock import exceptions
class MockerCore(object):
"""A wrapper around common mocking functions.
Automate the process of mocking the requests library. This will keep the
same general options available and prevent repeating code.
"""
_PROXY_FUNCS = set(['last_request',
'register_uri',
'request_history'])
def __init__(self, **kwargs):
self._adapter = adapter.Adapter()
self._real_http = kwargs.pop('real_http', False)
self._real_send = None
if kwargs:
raise TypeError('Unexpected Arguments: %s' % ', '.join(kwargs))
def start(self):
"""Start mocking requests.
Install the adapter and the wrappers required to intercept requests.
"""
if self._real_send:
raise RuntimeError('Mocker has already been started')
self._real_send = requests.Session.send
def _fake_get_adapter(session, url):
return self._adapter
def _fake_send(session, request, **kwargs):
real_get_adapter = requests.Session.get_adapter
requests.Session.get_adapter = _fake_get_adapter
try:
return self._real_send(session, request, **kwargs)
except exceptions.NoMockAddress:
if not self._real_http:
raise
finally:
requests.Session.get_adapter = real_get_adapter
return self._real_send(session, request, **kwargs)
requests.Session.send = _fake_send
def stop(self):
"""Stop mocking requests.
This should have no impact if mocking has not been started.
"""
if self._real_send:
requests.Session.send = self._real_send
self._real_send = None
def __getattr__(self, name):
if name in self._PROXY_FUNCS:
try:
return getattr(self._adapter, name)
except AttributeError:
pass
raise AttributeError(name)
class Mocker(MockerCore):
"""The standard entry point for mock Adapter loading.
"""
def __init__(self, **kwargs):
"""Create a new mocker adapter.
:param str kw: Pass the mock object through to the decorated function
as this named keyword argument, rather than a positional argument.
:param bool real_http: True to send the request to the real requested
uri if there is not a mock installed for it. Defaults to False.
"""
self._kw = kwargs.pop('kw', None)
super(Mocker, self).__init__(**kwargs)
def __enter__(self):
self.start()
return self
def __exit__(self, type, value, traceback):
self.stop()
def __call__(self, func):
@functools.wraps(func)
def inner(*args, **kwargs):
with self as m:
if self._kw:
kwargs[self._kw] = m
else:
args = list(args)
args.append(m)
return func(*args, **kwargs)
return inner

@ -12,7 +12,7 @@
import requests
import requests_mock
from requests_mock import fixture
from requests_mock.contrib import fixture
from requests_mock.tests import base
@ -22,9 +22,6 @@ class MockingTests(base.TestCase):
super(MockingTests, self).setUp()
self.mocker = self.useFixture(fixture.Fixture())
def test_basic_install(self):
pass
def test_failure(self):
self.assertRaises(requests_mock.NoMockAddress,
requests.get,

@ -0,0 +1,81 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import requests
import requests_mock
from requests_mock.tests import base
original_send = requests.Session.send
class MockerTests(base.TestCase):
def assertMockStarted(self):
self.assertNotEqual(original_send, requests.Session.send)
def assertMockStopped(self):
self.assertEqual(original_send, requests.Session.send)
def _do_test(self, m):
self.assertMockStarted()
m.register_uri('GET', 'http://www.test.com', text='resp')
resp = requests.get('http://www.test.com')
self.assertEqual('resp', resp.text)
def test_multiple_starts(self):
mocker = requests_mock.Mocker()
self.assertMockStopped()
mocker.start()
self.assertMockStarted()
self.assertRaises(RuntimeError, mocker.start)
mocker.stop()
self.assertMockStopped()
mocker.stop()
def test_with_context_manager(self):
self.assertMockStopped()
with requests_mock.Mocker() as m:
self._do_test(m)
self.assertMockStopped()
@mock.patch('requests.adapters.HTTPAdapter.send')
@requests_mock.Mocker(real_http=True)
def test_real_http(self, real_send, mocker):
url = 'http://www.google.com/'
real_send.return_value = requests.Response()
real_send.return_value.status_code = 200
requests.get(url)
self.assertEqual(1, real_send.call_count)
self.assertEqual(url, real_send.call_args[0][0].url)
@requests_mock.Mocker()
def test_with_test_decorator(self, m):
self._do_test(m)
@requests_mock.Mocker(kw='mock')
def test_with_mocker_kwargs(self, **kwargs):
self._do_test(kwargs['mock'])
def test_with_decorator(self):
@requests_mock.Mocker()
def inner(m):
self.assertMockStarted()
self._do_test(m)
self.assertMockStopped()
inner()
self.assertMockStopped()

@ -1,4 +1,5 @@
discover
fixtures
mock
testrepository>=0.0.18
testtools