Skip to content

Commit

Permalink
Calls info in BaseResponse (#664)
Browse files Browse the repository at this point in the history
* Add calls to BaseResponse
* Reuse calls for matched BaseResponse and add add_call method into CallList
* Reuse call object with add_call & add example of usage Response.calls into README

Co-authored-by: Maksim Beliaev <beliaev.m.s@gmail.com>
  • Loading branch information
zeezdev and beliaev-maksim committed Oct 30, 2023
1 parent 5c6a3b1 commit bcad0cd
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 5 deletions.
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():
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(f"{uid} updated with {response.status_code} status code")
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 @@ -247,6 +247,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 @@ -386,7 +389,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 @@ -502,6 +505,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 @@ -1052,14 +1063,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
30 changes: 30 additions & 0 deletions responses/tests/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2035,6 +2035,36 @@ def run():
assert_reset()


def test_response_calls_and_registry_calls_are_equal():
@responses.activate
def run():
rsp1 = responses.add(responses.GET, "http://www.example.com")
rsp2 = responses.add(responses.GET, "http://www.example.com/1")
rsp3 = responses.add(
responses.GET, "http://www.example.com/2"
) # won't be requested

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

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

run()
assert_reset()


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

0 comments on commit bcad0cd

Please sign in to comment.