.. 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)