1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
|
.. warning::
This component is an *alpha*. Interfaces may change outside of the
normal semver policy.
Getting Started with _testing
=============================
Dependencies
------------
This toolchain requires the ``responses`` library.
``globus_sdk._testing`` is tested to operate with the latest version of
``responses``.
Recommended Fixtures
--------------------
Under pytest, this is the recommended fixture for setting up responses and
guaranteeing that requests are sent to the production hostnames:
.. code-block:: python
@pytest.fixture(autouse=True)
def mocked_responses(monkeypatch):
responses.start()
monkeypatch.setitem(os.environ, "GLOBUS_SDK_ENVIRONMENT", "production")
yield
responses.stop()
responses.reset()
Usage
-----
Activating Individual Responses
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Once ``responses`` has been activated, each response fixture can be loaded and
activated by name:
.. code-block:: python
from globus_sdk._testing import load_response
# load_response will add the response to `responses` and return it
load_response("auth.get_identities")
# "case" is used to have a single name map to multiple responses
data = load_response("auth.get_identities", case="multiple")
Responses can also be activated by passing an SDK client method, bound or
unbound, as in:
.. code-block:: python
import globus_sdk
from globus_sdk._testing import load_response
load_response(globus_sdk.AuthClient.get_identities)
load_response(globus_sdk.AuthClient.get_identities, case="unauthorized")
# or, with a bound method
ac = globus_sdk.AuthClient()
load_response(ac.get_identities, case="multiple")
Activating "Scenarios"
~~~~~~~~~~~~~~~~~~~~~~
Some sets of fixtures may describe a scenario, and therefore it's desirable to
load all of them at once:
.. code-block:: python
from globus_sdk._testing import load_response_set
fixtures = load_response_set("scenario.foo")
Getting Responses and ResponseSets without Activating
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to fetch a ``ResponseSet`` or ``RegisteredResponse`` without
activating it, you can do this via the ``get_response_set`` method. Responses
must always be part of a response set, and the default name for an individual
response is ``"default"``.
.. code-block:: python
from globus_sdk import AuthClient
from globus_sdk._testing import get_response_set
# rset will not be activated
rset = get_response_set(AuthClient.get_identities)
# you can get an individual response from rset
get_ids = rset.get("default")
# you can manually activate a whole set
rset.activate_all()
# or just one response from it by name
rset.activate("default")
Note that activating a whole response set may or may not make sense. For
example, the response set for ``AuthClient.get_identities`` provides various
responses for the same API call.
Registering Response Sets
~~~~~~~~~~~~~~~~~~~~~~~~~
You can register your own response sets dynamically, and then load them up with
the same ``load_response_set`` method. Note that custom response sets will
override the builtin response sets, if names match.
.. code-block:: python
from globus_sdk._testing import load_response_set, register_response_set
import uuid
# register a scenario under which Globus Auth get_identities and Globus
# Transfer operation_ls both return payloads of `{"foo": "bar"}`
# use an autogenerated endpoint ID and put it into the response metadata
# register_response_set takes dict data and converts it to fixtures
endpoint_id = str(uuid.uuid1())
register_response_set(
"foobar",
{
"get_identities": {
"service": "auth",
"path": "/v2/api/identities",
"json": {"foo": "bar"},
},
"operation_ls": {
"service": "transfer",
"path": f"/operation/endpoint/{endpoint_id}/ls",
"json": {"foo": "bar"},
},
},
metadata={
"endpoint_id": endpoint_id,
},
)
# activate the result, and get it as a ResponseSet
fixtures = load_response_set("foobar")
# you can then pull the epid from the metadata
epid = fixtures.metadata["endpoint_id"]
transfer_client.operation_ls(epid)
``register_response_set`` can therefore be used to load fixture data early in
a tetstsuite run (e.g. as an autouse session-level fixture), for reference
later in the testsuite.
Loading Responses without Registering
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Because ``RegisteredResponse`` takes care of resolving ``"auth"`` to the Auth
URL, ``"transfer"`` to the Transfer URL, and so forth, you might want to use
``globus_sdk._testing`` in lieu of ``responses`` even when registering single
responses for individual tests.
To support this mode of usage, ``load_response`` can take a
``RegisteredResponse`` instance, and ``load_response_set`` can take a
``ResponseSet`` instance.
Consider the following example of a parametrized test which uses
``load_response(RegisteredResponse(...))`` as a replacement for
``responses.add``:
.. code-block:: python
from globus_sdk._testing import load_response, RegisteredResponse
import pytest
@pytest.mark.parametrize("message", ["foo", "bar"])
def test_get_identities_sends_back_strange_message(message):
load_response(
RegisteredResponse(
service="auth",
path="/v2/api/identities",
json={"message": message},
)
)
ac = globus_sdk.AuthClient()
res = ac.get_identities(usernames="foo@example.com")
assert res["message"] == message
In this mode of usage, the response set registry is skipped altogether. It is
not necessary to name or organize the response fixtures in a way that is usable
outside of the specific test.
Using non-default responses.RequestsMock objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, all methods in ``globus_sdk._testing`` which converse with
``responses`` use the default mock. This is the behavior offered by
``responses.add(...)`` and similar methods.
However, you can pass a custom ``RequestsMock`` if so desired to the following
methods:
* ``get_last_request``
* ``load_response_set``
* ``load_response``
as a keyword argument, ``requests_mock``.
e.g.
.. code-block:: python
from globus_sdk._testing import get_last_request
import responses
custom_mock = responses.RequestsMock(...)
...
get_last_request(requests_mock=custom_mock)
|