diff --git a/README.rst b/README.rst index 06827b2..1285eb4 100644 --- a/README.rst +++ b/README.rst @@ -17,28 +17,17 @@ requests-mock Intro ===== -`requests-mock` provides a simple way to stub out the HTTP portions or your testing code. +`requests-mock` provides a building block to stub out the HTTP `requests`_ portions of your testing code. - -What is it +The Basics ========== -The `requests`_ library has the concept of `pluggable transport adapters`_. -These adapters allow you to register your own handlers for different URIs or protocols. +Everything in `requests`_ eventually goes through an adapter to do the transport work. +`requests-mock` creates a custom `adatper` that allows you to predefine responses when certain URIs are called. -The *requests-mock* library at its core is simply a transport adapter that can be preloaded with responses that are returned if certain URIs are requested. -This is particularly useful in unit tests where you want to return known responses from HTTP requests without making actual calls. +There are then a number of methods provided to get the adapter used. -As the `requests`_ library has very limited options for how to load and use adapters *requests-mock* also provides a number (currently 1) of ways that to make sure the mock adapter is used. -These are only loading mechanisms, they do not contain any logic and can be used as a reference to load the adapter in whatever ways works best for your project. - -Adapter Usage -============= - -Creating an Adapter -------------------- - -The standard `requests`_ means of using an adapter is to mount it on a created session. This is not the only way to load the adapter, however the same interactions will be used. +A simple example: .. code:: python @@ -46,160 +35,32 @@ The standard `requests`_ means of using an adapter is to mount it on a created s >>> import requests_mock >>> session = requests.Session() - >>> adapter = requests_mock.Adapter() + >>> adapter = requests_mock.Adater() >>> session.mount('mock', adapter) -At this point any requests made by the session to a URI starting with `mock://` will be sent to our adapter. - - -Registering Responses ---------------------- - -Responses are registered with the `register_uri` function on the adapter. - -.. code:: python - - >>> adapter.register_uri('GET', 'mock://test.com', text='Success') + >>> adapter.register_uri('GET', 'mock://test.com', text='data') >>> resp = session.get('mock://test.com') - >>> resp.text - 'Success' + >>> resp.status_code, resp.text + (200, 'data') -`register_uri` takes the HTTP method, the URI and then information that is used to build the response. This information includes: +Obviously having all URLs be `mock://` prefixed isn't going to useful, so there are a number of ways to get the adapter into place. -:status_code: The HTTP status response to return. Defaults to 200. -:reason: The reason text that accompanies the Status (e.g. 'OK' in '200 OK') -:headers: A dictionary of headers to be included in the response. +For more information checkout the `docs`_. -To specify the body of the response there are a number of options that depend on the format that you wish to return. +License +======= -:json: A python object that will be converted to a JSON string. -:text: A unicode string. This is typically what you will want to use for regular textual content. -:content: A byte string. This should be used for including binary data in responses. -:body: A file like object that contains a `.read()` function. -:raw: A prepopulated urllib3 response to be returned. +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 -These options are named to coincide with the parameters on a `requests.Response` object. For example: + http://www.apache.org/licenses/LICENSE-2.0 -.. code:: python - - >>> adapter.register_uri('GET', 'mock://test.com/1', json={'a': 'b'}, status_code=200) - >>> resp = session.get('mock://test.com/1') - >>> resp.json() - {'a': 'b'} - - >>> adapter.register_uri('GET', 'mock://test.com/2', text='Not Found', status_code=404) - >>> resp = session.get('mock://test.com/2') - >>> resp.text - 'Not Found' - >>> resp.status_code - 404 - -It only makes sense to provide at most one body element per response. - -Dynamic Response ----------------- - -A callback can be provided in place of any of the body elements. -Callbacks must be a function in the form of - -.. code:: python - - def callback(request, context): - -and return a value suitable to the body element that was specified. -The elements provided are: - -:request: The `requests.Request` object that was provided. -:context: An object containing the collected known data about this response. - -The available properties on the `context` are: - -:headers: The dictionary of headers that are to be returned in the response. -:status_code: The status code that is to be returned in the response. -:reason: The string HTTP status code reason that is to be returned in the response. - -These parameters are populated initially from the variables provided to the `register_uri` function and if they are modified on the context object then those changes will be reflected in the response. - -.. code:: python - - >>> def text_callback(request, context): - ... context.status_code = 200 - ... context.headers['Test1'] = 'value1' - ... return 'response' - ... - >>> adapter.register_uri('GET', 'mock://test.com/3', text=text_callback, headers={'Test2': 'value2'}, status_code=400) - >>> resp = session.get('mock://test.com/3') - >>> resp.status_code, resp.headers, resp.text - (200, {'Test1': 'value1', 'Test2': 'value2'}, 'response') - -Response Lists --------------- - -Multiple responses can be provided to be returned in order by specifying the keyword parameters in a list. -If the list is exhausted then the last response will continue to be returned. - -.. code:: python - - >>> adapter.register_uri('GET', 'mock://test.com/4', [{'text': 'resp1', 'status_code': 300}, - ... {'text': 'resp2', 'status_code': 200}]) - >>> resp = session.get('mock://test.com/4') - >>> (resp.status_code, resp.text) - (300, 'resp1') - >>> resp = session.get('mock://test.com/4') - >>> (resp.status_code, resp.text) - (200, 'resp2') - >>> resp = session.get('mock://test.com/4') - >>> (resp.status_code, resp.text) - (200, 'resp2') - - -Request Matching -================ - -Whilst it is preferable to provide the whole URI to `register_uri` it is possible to just specify components. - -You can specify a protocol-less path: - -.. code:: python - - >>> adapter.register_uri('GET', '//test.com/5', text='resp') - >>> session.get('mock://test.com/5').text - 'resp' - -or you can specify just a path: - -.. code:: python - - >>> adapter.register_uri('GET', '/6', text='resp') - >>> session.get('mock://test.com/6').text - 'resp' - >>> session.get('mock://another.com/6').text - 'resp' - -Query strings provided to a register will match so long as at least those provided form part of the request. - -.. code:: python - - >>> adapter.register_uri('GET', '/7?a=1', text='resp') - >>> session.get('mock://test.com/7?a=1&b=2').text - 'resp' - - >>> session.get('mock://test.com/7?a=3') - Traceback (most recent call last): - ... - requests_mock.exceptions.NoMockAddress: No mock address: GET mock://test.com/7?a=3 - -This can be a problem in certain situations, so if you wish to match only the complete query string there is a flag `complete_qs`: - -.. code:: python - - >>> adapter.register_uri('GET', '/8?a=1', complete_qs=True, text='resp') - >>> session.get('mock://test.com/8?a=1&b=2') - Traceback (most recent call last): - ... - requests_mock.exceptions.NoMockAddress: No mock address: GET mock://test.com/8?a=1&b=2 +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. .. _requests: http://python-requests.org -.. _pluggable transport adapters: http://docs.python-requests.org/en/latest/user/advanced/#transport-adapters - - +.. _docs: http://requests-mock.readthedocs.org diff --git a/docs/adapter.rst b/docs/adapter.rst new file mode 100644 index 0000000..7e5d11b --- /dev/null +++ b/docs/adapter.rst @@ -0,0 +1,126 @@ +============= +Adapter Usage +============= + +Creating an Adapter +=================== + +The standard `requests`_ means of using an adapter is to :py:meth:`requests.Session.mount` it on a created session. This is not the only way to load the adapter, however the same interactions will be used. + +.. code:: python + + >>> import requests + >>> import requests_mock + + >>> session = requests.Session() + >>> adapter = requests_mock.Adapter() + >>> session.mount('mock', adapter) + +At this point any requests made by the session to a URI starting with `mock://` will be sent to our adapter. + + +Registering Responses +===================== + +Responses are registered with the :py:meth:`requests_mock.Adapter.register_uri` function on the adapter. + +.. code:: python + + >>> adapter.register_uri('GET', 'mock://test.com', text='Success') + >>> resp = session.get('mock://test.com') + >>> resp.text + 'Success' + +:py:meth:`requests_mock.Adapter.register_uri` takes the HTTP method, the URI and then information that is used to build the response. This information includes: + +:status_code: The HTTP status response to return. Defaults to 200. +:reason: The reason text that accompanies the Status (e.g. 'OK' in '200 OK') +:headers: A dictionary of headers to be included in the response. + +To specify the body of the response there are a number of options that depend on the format that you wish to return. + +:json: A python object that will be converted to a JSON string. +:text: A unicode string. This is typically what you will want to use for regular textual content. +:content: A byte string. This should be used for including binary data in responses. +:body: A file like object that contains a `.read()` function. +:raw: A prepopulated :py:class:`urllib3.response.HTTPResponse` to be returned. + +These options are named to coincide with the parameters on a :py:class:`requests.Response` object. For example: + +.. code:: python + + >>> adapter.register_uri('GET', 'mock://test.com/1', json={'a': 'b'}, status_code=200) + >>> resp = session.get('mock://test.com/1') + >>> resp.json() + {'a': 'b'} + + >>> adapter.register_uri('GET', 'mock://test.com/2', text='Not Found', status_code=404) + >>> resp = session.get('mock://test.com/2') + >>> resp.text + 'Not Found' + >>> resp.status_code + 404 + +It only makes sense to provide at most one body element per response. + +Dynamic Response +================ + +A callback can be provided in place of any of the body elements. +Callbacks must be a function in the form of + +.. code:: python + + def callback(request, context): + +and return a value suitable to the body element that was specified. +The elements provided are: + +:request: The :py:class:`requests.Request` object that was provided. +:context: An object containing the collected known data about this response. + +The available properties on the `context` are: + +:headers: The dictionary of headers that are to be returned in the response. +:status_code: The status code that is to be returned in the response. +:reason: The string HTTP status code reason that is to be returned in the response. + +These parameters are populated initially from the variables provided to the :py:meth:`requests_mock.Adapter.register_uri` function and if they are modified on the context object then those changes will be reflected in the response. + +.. code:: python + + >>> def text_callback(request, context): + ... context.status_code = 200 + ... context.headers['Test1'] = 'value1' + ... return 'response' + ... + >>> adapter.register_uri('GET', + ... 'mock://test.com/3', + ... text=text_callback, + ... headers={'Test2': 'value2'}, + ... status_code=400) + >>> resp = session.get('mock://test.com/3') + >>> resp.status_code, resp.headers, resp.text + (200, {'Test1': 'value1', 'Test2': 'value2'}, 'response') + +Response Lists +============== + +Multiple responses can be provided to be returned in order by specifying the keyword parameters in a list. +If the list is exhausted then the last response will continue to be returned. + +.. code:: python + + >>> adapter.register_uri('GET', 'mock://test.com/4', [{'text': 'resp1', 'status_code': 300}, + ... {'text': 'resp2', 'status_code': 200}]) + >>> resp = session.get('mock://test.com/4') + >>> (resp.status_code, resp.text) + (300, 'resp1') + >>> resp = session.get('mock://test.com/4') + >>> (resp.status_code, resp.text) + (200, 'resp2') + >>> resp = session.get('mock://test.com/4') + >>> (resp.status_code, resp.text) + (200, 'resp2') + +.. _requests: http://python-requests.org diff --git a/docs/conf.py b/docs/conf.py index d055653..3aab35b 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -38,7 +38,9 @@ import requests_mock # noqa # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -261,3 +263,8 @@ texinfo_documents = [ # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False + +requests_uri = 'http://docs.python-requests.org/en/latest/' +urllib3_uri = 'http://urllib3.readthedocs.org/en/latest' +intersphinx_mapping = {'requests': (requests_uri, None), + 'urllib3': (urllib3_uri, None)} diff --git a/docs/fixture.rst b/docs/fixture.rst new file mode 100644 index 0000000..bf7e487 --- /dev/null +++ b/docs/fixture.rst @@ -0,0 +1,41 @@ +======= +Fixture +======= + +`Fixtures`_ provide a way to create reusable state and helper methods in test cases. + +To use the *requests-mock* fixture your tests need to have a dependency on the `fixtures`_ library and the `mock`_ library. +These are not provided by *requests-mock*. + +Overview +======== + +The fixture mocks the :py:meth:`requests.Session.get_adapter` method so that all requests will be served by the mock adapter. + +The fixture provides the same interfaces as the adapter. + +.. code:: python + + >>> import requests + >>> from requests_mock import fixture + >>> import testtools + + >>> class MyTestCase(testtools.TestCase): + + ... TEST_URL = 'http://www.google.com' + + ... def setUp(self): + ... super(MyTestCase, self).setUp() + ... self.requests_mock = self.useFixture(requests_mock.Mock()) + ... self.requests_mock.register_uri('GET', self.TEST_URL, text='respA') + ... + ... def test_method(self): + ... self.requests_mock.register_uri('POST', self.TEST_URL, text='respB') + ... resp = requests.get(self.TEST_URL) + ... self.assertEqual('respA', resp.text) + ... self.assertEqual(self.TEST_URL, self.requests_mock.last_request.url) + ... + + +.. _Fixtures: https://pypi.python.org/pypi/fixtures +.. _mock: https://pypi.python.org/pypi/mock diff --git a/docs/index.rst b/docs/index.rst index 4fc5b5f..2f42bff 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,7 +1,3 @@ -.. complexity documentation master file, created by - sphinx-quickstart on Tue Jul 9 22:26:36 2013. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. Welcome to requests-mock's documentation! ====================================== @@ -9,9 +5,13 @@ Welcome to requests-mock's documentation! Contents: .. toctree:: - :maxdepth: 2 + :maxdepth: 3 - readme + overview + adapter + matching + + loading Indices and tables ================== diff --git a/docs/loading.rst b/docs/loading.rst new file mode 100644 index 0000000..f0f6e84 --- /dev/null +++ b/docs/loading.rst @@ -0,0 +1,9 @@ +=============== +Adapter Loading +=============== + +Loading can be accomplished in a number of ways: + +.. toctree:: + + fixture diff --git a/docs/matching.rst b/docs/matching.rst new file mode 100644 index 0000000..e5237a4 --- /dev/null +++ b/docs/matching.rst @@ -0,0 +1,52 @@ +================ +Request Matching +================ + +Whilst it is preferable to provide the whole URI to :py:meth:`requests_mock.Adapter.register_uri` it is possible to just specify components. + +Basic +===== + +You can specify a protocol-less path: + +.. code:: python + + >>> adapter.register_uri('GET', '//test.com/5', text='resp') + >>> session.get('mock://test.com/5').text + 'resp' + +or you can specify just a path: + +.. code:: python + + >>> adapter.register_uri('GET', '/6', text='resp') + >>> session.get('mock://test.com/6').text + 'resp' + >>> session.get('mock://another.com/6').text + 'resp' + +Query Strings +============= + +Query strings provided to a register will match so long as at least those provided form part of the request. + +.. code:: python + + >>> adapter.register_uri('GET', '/7?a=1', text='resp') + >>> session.get('mock://test.com/7?a=1&b=2').text + 'resp' + + >>> session.get('mock://test.com/7?a=3') + Traceback (most recent call last): + ... + requests_mock.exceptions.NoMockAddress: No mock address: GET mock://test.com/7?a=3 + +This can be a problem in certain situations, so if you wish to match only the complete query string there is a flag `complete_qs`: + +.. code:: python + + >>> adapter.register_uri('GET', '/8?a=1', complete_qs=True, text='resp') + >>> session.get('mock://test.com/8?a=1&b=2') + Traceback (most recent call last): + ... + requests_mock.exceptions.NoMockAddress: No mock address: GET mock://test.com/8?a=1&b=2 diff --git a/docs/overview.rst b/docs/overview.rst new file mode 100644 index 0000000..ce1a2cd --- /dev/null +++ b/docs/overview.rst @@ -0,0 +1,15 @@ +======== +Overview +======== + +The `requests`_ library has the concept of `pluggable transport adapters`_. +These adapters allow you to register your own handlers for different URIs or protocols. + +The *requests-mock* library at its core is simply a transport adapter that can be preloaded with responses that are returned if certain URIs are requested. +This is particularly useful in unit tests where you want to return known responses from HTTP requests without making actual calls. + +As the `requests`_ library has very limited options for how to load and use adapters *requests-mock* also provides a number (currently 1) of ways that to make sure the mock adapter is used. +These are only loading mechanisms, they do not contain any logic and can be used as a reference to load the adapter in whatever ways works best for your project. + +.. _requests: http://python-requests.org +.. _pluggable transport adapters: http://docs.python-requests.org/en/latest/user/advanced/#transport-adapters diff --git a/docs/readme.rst b/docs/readme.rst index 6b2b3ec..72a3355 100644 --- a/docs/readme.rst +++ b/docs/readme.rst @@ -1 +1 @@ -.. include:: ../README.rst \ No newline at end of file +.. include:: ../README.rst