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:
parent
d826ba6f82
commit
833ee180c3
11
docs/contrib.rst
Normal file
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
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
requests_mock/contrib/__init__.py
Normal file
0
requests_mock/contrib/__init__.py
Normal file
27
requests_mock/contrib/fixture.py
Executable file
27
requests_mock/contrib/fixture.py
Executable file
@ -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
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,
|
||||
|
81
requests_mock/tests/test_mocker.py
Normal file
81
requests_mock/tests/test_mocker.py
Normal file
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user