Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Calls info in BaseResponse #664

Merged
merged 17 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
0.24.0
------

* Added `BaseResponse.calls` to access calls data of a separate mocked request. See #664
* Added `real_adapter_send` parameter to `RequestsMock` that will allow users to set
through which function they would like to send real requests
* Added support for re.Pattern based header matching.
Expand Down
53 changes: 53 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,59 @@ Assert that the request was called exactly n times.
responses.assert_call_count("http://www.example.com?hello=world", 1) is True


Assert Request Calls data
------------------

``Request`` object has ``calls`` list which elements correspond to ``Call`` objects
in the global list of ``Registry``. This can be useful when the order of requests is not
guaranteed, but you need to check their correctness, for example in multithreaded
applications.

.. code-block:: python

import concurrent.futures
import responses
import requests


@responses.activate
def test_assert_calls_on_resp():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that this example might look very close to what you have in production. But can we unload it a bit and reduce complexity?

we probably still want to show an example of concurrent since this could be one of the main usages, but we do not need all those complicated matrices of settings to show the usage

can you please simplify it to remove additional arguments and have more clear names like
rsp1, rsp2, rsp3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got you, I'll try to simplify this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simplified, please take a look

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zeezdev now looks a way better, thank you!

rsp1 = responses.patch("http://www.foo.bar/1/", status=200)
rsp2 = responses.patch("http://www.foo.bar/2/", status=400)
rsp3 = responses.patch("http://www.foo.bar/3/", status=200)

def update_user(uid, is_active):
url = f"http://www.foo.bar/{uid}/"
response = requests.patch(url, json={"is_active": is_active})
return response

with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
future_to_uid = {
executor.submit(update_user, uid, is_active): uid
for (uid, is_active) in [("3", True), ("2", True), ("1", False)]
}
for future in concurrent.futures.as_completed(future_to_uid):
uid = future_to_uid[future]
response = future.result()
print("%s updated with %d status code" % (uid, response.status_code))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please update it with f-string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


assert len(responses.calls) == 3 # total calls count

assert rsp1.call_count == 1
assert rsp1.calls[0] in responses.calls
assert rsp1.calls[0].response.status_code == 200
assert json.loads(rsp1.calls[0].request.body) == {"is_active": False}

assert rsp2.call_count == 1
assert rsp2.calls[0] in responses.calls
assert rsp2.calls[0].response.status_code == 400
assert json.loads(rsp2.calls[0].request.body) == {"is_active": True}

assert rsp3.call_count == 1
assert rsp3.calls[0] in responses.calls
assert rsp3.calls[0].response.status_code == 200
assert json.loads(rsp3.calls[0].request.body) == {"is_active": True}

Multiple Responses
------------------

Expand Down
23 changes: 18 additions & 5 deletions responses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,9 @@ def __getitem__(self, idx: Union[int, slice]) -> Union[Call, List[Call]]:
def add(self, request: "PreparedRequest", response: "_Body") -> None:
self._calls.append(Call(request, response))

def add_call(self, call: Call) -> None:
self._calls.append(call)

def reset(self) -> None:
self._calls = []

Expand Down Expand Up @@ -384,7 +387,7 @@ def __init__(
)

self.match: "_MatcherIterable" = match
self.call_count: int = 0
self._calls: CallList = CallList()
self.passthrough = passthrough

def __eq__(self, other: Any) -> bool:
Expand Down Expand Up @@ -500,6 +503,14 @@ def matches(self, request: "PreparedRequest") -> Tuple[bool, str]:

return True, ""

@property
def call_count(self) -> int:
return len(self._calls)

@property
def calls(self) -> CallList:
return self._calls


def _form_response(
body: Union[BufferedReader, BytesIO],
Expand Down Expand Up @@ -1048,14 +1059,16 @@ def _on_request(
request, match.get_response(request)
)
except BaseException as response:
match.call_count += 1
self._calls.add(request, response)
call = Call(request, response)
self._calls.add_call(call)
match.calls.add_call(call)
raise

if resp_callback:
response = resp_callback(response) # type: ignore[misc]
match.call_count += 1
self._calls.add(request, response) # type: ignore[misc]
call = Call(request, response) # type: ignore[misc]
self._calls.add_call(call)
match.calls.add_call(call)

retries = retries or adapter.max_retries
# first validate that current request is eligible to be retried.
Expand Down
83 changes: 83 additions & 0 deletions responses/tests/test_responses.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import inspect
import json
import os
import re
import warnings
Expand Down Expand Up @@ -2035,6 +2036,88 @@ def run():
assert_reset()


def test_response_and_requests_mock_calls_are_equal():
@responses.activate
def run():
rsp = responses.add(responses.GET, "http://www.example.com")
rsp2 = responses.add(responses.GET, "http://www.example.com/1")

requests.get("http://www.example.com")
requests.get("http://www.example.com/1")

assert len(responses.calls) == 2
assert rsp.call_count == 1
assert rsp.calls[0] is responses.calls[0]
assert rsp2.call_count == 1
assert rsp2.calls[0] is responses.calls[1]

run()
assert_reset()


def test_response_call_request():
@responses.activate
def run():
rsp1 = responses.add(responses.GET, "http://www.example.com")
rsp2 = responses.add(
responses.PUT, "http://www.foo.bar/42/", json={"id": 42, "name": "Bazz"}
)
request_payload = {"name": "Bazz"}

requests.get("http://www.example.com")
requests.get("http://www.example.com?hello=world")
requests.put(
"http://www.foo.bar/42/",
json=request_payload,
)

assert rsp1.call_count == 2
rsp1_request1 = rsp1.calls[0].request
assert rsp1_request1.url == "http://www.example.com/"
assert rsp1_request1.method == "GET"
rsp1_request2 = rsp1.calls[1].request
assert rsp1_request2.url == "http://www.example.com/?hello=world"
assert rsp1_request2.method == "GET"
assert rsp2.call_count == 1
rsp2_request1 = rsp2.calls[0].request
assert rsp2_request1.url == "http://www.foo.bar/42/"
assert rsp2_request1.method == "PUT"
assert json.loads(rsp2_request1.body) == request_payload

run()
assert_reset()


def test_response_call_response():
@responses.activate
def run():
rsp1 = responses.add(responses.GET, "http://www.example.com", body=b"test")
rsp2 = responses.add(
responses.POST,
"http://www.foo.bar/42/",
json={"id": 42, "name": "Bazz"},
status=201,
)

requests.get("http://www.example.com")
requests.post(
"http://www.foo.bar/42/",
json={"name": "Bazz"},
)

assert rsp1.call_count == 1
rsp1_response = rsp1.calls[0].response
assert rsp1_response.content == b"test"
assert rsp1_response.status_code == 200
assert rsp2.call_count == 1
rsp2_response = rsp2.calls[0].response
assert rsp2_response.json() == {"id": 42, "name": "Bazz"}
assert rsp2_response.status_code == 201

run()
assert_reset()


def test_fail_request_error():
"""
Validate that exception is raised if request URL/Method/kwargs don't match
Expand Down