diff --git a/docs/adapter.rst b/docs/adapter.rst index 5623491..89ea3d8 100644 --- a/docs/adapter.rst +++ b/docs/adapter.rst @@ -18,127 +18,4 @@ The standard `requests`_ means of using an adapter is to :py:meth:`~requests.Ses 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. - -.. doctest:: - - >>> 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: - -.. doctest:: - - >>> 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. - -.. doctest:: - - >>> 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. - -.. doctest:: - - >>> 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') - -Mock Returns -============ - -The object returned from a :py:meth:`~requests_mock.Adapter.register_uri` is an object representing the mock that was created at that URL. -There are a couple of queries that can be made of this object, including `called` and `call_count`. - -.. doctest:: - - >>> mock_obj = adapter.register_uri('GET', 'mock://test.com/5', text='resp') - >>> resp = session.get('mock://test.com/5') - >>> resp.text - 'resp' - >>> mock_obj.called - True - >>> mock_obj.call_count - 1 - .. _requests: http://python-requests.org - diff --git a/docs/api/requests_mock.rst b/docs/api/requests_mock.rst index 221ebe7..e34aafe 100644 --- a/docs/api/requests_mock.rst +++ b/docs/api/requests_mock.rst @@ -25,7 +25,7 @@ requests_mock.exceptions module requests_mock.fixture module ---------------------------- -.. automodule:: requests_mock.fixture +.. automodule:: requests_mock.contrib.fixture :members: :undoc-members: :show-inheritance: diff --git a/docs/history.rst b/docs/history.rst new file mode 100644 index 0000000..5134966 --- /dev/null +++ b/docs/history.rst @@ -0,0 +1,47 @@ +=============== +Request History +=============== + +The object returned from creating a mock or registering a URI in an adapter is capable of tracking and querying the history of requests that this mock responded to. + +Called +====== + +The easiest way to test if a request hit the adapter is to simply check the called property. + +.. doctest:: + + >>> import requests + >>> import requests_mock + + >>> with requests_mock.mock() as m: + ... m.get('http://test.com, text='resp') + ... resp = requests.get('http://test.com') + ... + >>> m.called + True + >>> m.call_count + 1 + +Request Objects +=============== + +The history of objects that passed through the `mocker`/`adapter` can also be retrieved + +.. doctest:: + + >>> history = m.request_history + >>> len(history) + 1 + >>> history[0].method + 'GET' + >>> history[0].url + 'http://test.com/' + +This request history object is a wrapper around a standard :py:class:`requests.Request` object with some additional information that make the interface more workable (as the :py:class:`~requests.Request` object is generally not dealt with by users. + +These additions include: + +:text: The data of the request converted into a unicode string. +:json: The data of the request loaded from json into python objects. +:qs: The query string of the request. See :py:meth:`urllib.parse.parse_qs` for information on the return format. diff --git a/docs/index.rst b/docs/index.rst index a463907..cfcb1f6 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,9 +8,11 @@ Contents: :maxdepth: 2 overview - adapter - matching mocker + matching + response + history + adapter contrib Indices and tables diff --git a/docs/matching.rst b/docs/matching.rst index 1b8d9e3..fe418c8 100644 --- a/docs/matching.rst +++ b/docs/matching.rst @@ -14,8 +14,20 @@ The examples in this file are loaded with: >>> session = requests.Session() >>> session.mount('mock', adapter) -Basic -===== +.. note:: + + The examples within use this syntax because request matching is a function of the adapter and not the mocker. + All the same arguments can be provided to the mocker if that is how you use `requests_mock` within your project, and use the + + .. code:: python + + mock.get(url, ...) + + form in place of the given: + + .. code:: python + + adapter.register_uri('GET', url, ...) .. doctest:: :hide: @@ -26,6 +38,22 @@ Basic >>> session = requests.Session() >>> session.mount('mock', adapter) +Simple +====== + +The most simple way to match a request is to register the URL and method that will be requested with a textual response. +When a request is made that goes through the mocker this response will be retrieved. + +.. doctest:: + + .. >>> adapter.register_uri('GET', 'mock://test.com/path', text='resp') + .. >>> session.get('mock://test.com/path').text + .. 'resp' + +Path Matching +============= + + You can specify a protocol-less path: .. doctest:: diff --git a/docs/mocker.rst b/docs/mocker.rst index 383773c..60c1d96 100644 --- a/docs/mocker.rst +++ b/docs/mocker.rst @@ -1,11 +1,17 @@ -============== -Mocker Loading -============== +================ +Using the Mocker +================ -Loading of the Adapter is handled by the :py:class:`requests_mock.Mocker` class, which provides two ways to load an adapter. +The mocker is a loading mechanism to ensure the adapter is correctly in place to intercept calls from requests. +It's goal is to provide an interface that is as close to the real requests library interface as possible. + +Activation +========== + +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. @@ -15,13 +21,13 @@ The Mocker object can work as a context manager. >>> import requests_mock >>> with requests_mock.Mocker() as m: - ... m.register_uri('GET', 'http://test.com', text='resp') + ... m.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. @@ -29,7 +35,7 @@ Mocker can also be used as a decorator. The created object will then be passed a >>> @requests_mock.Mocker() ... def test_function(m): - ... m.register_uri('GET', 'http://test.com', text='resp') + ... m.get('http://test.com', text='resp') ... return requests.get('http://test.com').text ... >>> test_function() @@ -41,7 +47,7 @@ If the position of the mock is likely to conflict with other arguments you can p >>> @requests_mock.Mocker(kw='mock') ... def test_kw_function(**kwargs): - ... kwargs['mock'].register_uri('GET', 'http://test.com', text='resp') + ... kwargs['mock'].get('http://test.com', text='resp') ... return requests.get('http://test.com').text ... >>> test_kw_function() @@ -81,7 +87,7 @@ This behavior mimics how patchers from `mock` library works. Methods ======= -The mocker object can be used with a similar interface to requests itself. Mocker objects can be called with simply the +The mocker object can be used with a similar interface to requests itself. .. doctest:: @@ -102,10 +108,13 @@ The functions exist for the common HTTP method: - :py:meth:`~requests_mock.MockerCore.post` - :py:meth:`~requests_mock.MockerCore.put` -As well as the base: +As well as the basic: - :py:meth:`~requests_mock.MockerCore.request` + - :py:meth:`~requests_mock.MockerCore.register_uri` +These methods correspond to the HTTP method of your request, so to mock POST requests you would use the :py:meth:`~requests_mock.MockerCore.post` function. +Futher information about what can be matched from a request can be found at :doc:`matching` Real HTTP Requests ================== diff --git a/docs/overview.rst b/docs/overview.rst index ce1a2cd..ac060e6 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -8,7 +8,7 @@ These adapters allow you to register your own handlers for different URIs or pro 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. +As the `requests`_ library has very limited options for how to load and use adapters *requests-mock* also provides a number of ways 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 diff --git a/docs/response.rst b/docs/response.rst new file mode 100644 index 0000000..5f4ed83 --- /dev/null +++ b/docs/response.rst @@ -0,0 +1,132 @@ +================== +Creating Responses +================== + +.. note:: + + The examples within use this syntax because response creation is a function of the adapter and not the mocker. + All the same arguments can be provided to the mocker if that is how you use `requests_mock` within your project, and use the + + .. code:: python + + mock.get(url, ...) + + form in place of the given: + + .. code:: python + + adapter.register_uri('GET', url, ...) + +Registering Responses +===================== + +Responses are registered with the :py:meth:`requests_mock.Adapter.register_uri` function on the adapter. + +.. doctest:: + + >>> 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: + +.. doctest:: + + >>> 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. + +.. doctest:: + + >>> 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. + +.. doctest:: + + >>> 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') + + +Callbacks work within response lists in exactly the same way they do normally; + +.. doctest:: + + >>> adapter.register_uri('GET', 'mock://test.com/5', [{'text': text_callback}]), + >>> resp = session.get('mock://test.com/5') + >>> resp.status_code, resp.headers, resp.text + (200, {'Test1': 'value1', 'Test2': 'value2'}, 'response')