Split docs for better RTFD viewing

Simplified README, not sure how wise that is.
This commit is contained in:
Jamie Lennox 2014-06-16 22:36:56 +10:00
parent 5ec896d63f
commit 4c97632c53
9 changed files with 282 additions and 171 deletions

View File

@ -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

126
docs/adapter.rst Normal file
View File

@ -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

View File

@ -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)}

41
docs/fixture.rst Normal file
View File

@ -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

View File

@ -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
==================

9
docs/loading.rst Normal file
View File

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

52
docs/matching.rst Normal file
View File

@ -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

15
docs/overview.rst Normal file
View File

@ -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

View File

@ -1 +1 @@
.. include:: ../README.rst
.. include:: ../README.rst