Repository: getsentry/responses
Pull request #739 (2024-09-12 20:17:04)

Export names `matchers` and `Response` from RequestsMock instances
This makes it possible to import or access only RequestsMock (possibly via a pytest fixture) and still be able to easily access the matchers or assemble a Response object.

It also makes it easier to adopt pytest-responses which uses `responses` as the fixture variable which would conflict with an import of this module.

Add test cases that mask the existing imports and still access matchers or assemble a Response object via the RequestsMock instance.

Also recommend using pytest-responses for a pytest fixture. Rather than explaining how to roll your own in a Markdown snippet, recommend using the companion package to get a standard `responses` pytest fixture easily usable in test cases.

filbranden: See somewhat related getsentry/pytest-responses#30 proposing some usability improvements in pytest-responses as well.

Patch:
diff --git a/README.rst b/README.rst
index 9b25507..da473bf 100644
--- a/README.rst
+++ b/README.rst
@@ -899,16 +899,19 @@ Integration with unit test frameworks
 Responses as a ``pytest`` fixture
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
+Use the pytest-responses package to export ``responses`` as a pytest fixture.
+
+``pip install pytest-responses``
+
+You can then access it in a pytest script using:
+
 .. code-block:: python
 
-    @pytest.fixture
-    def mocked_responses():
-        with responses.RequestsMock() as rsps:
-            yield rsps
+    import pytest_responses
 
 
-    def test_api(mocked_responses):
-        mocked_responses.get(
+    def test_api(responses):
+        responses.get(
             "http://twitter.com/api/1/foobar",
             body="{}",
             status=200,
diff --git a/responses/__init__.py b/responses/__init__.py
index fb37516..bcfc19d 100644
--- a/responses/__init__.py
+++ b/responses/__init__.py
@@ -714,6 +714,11 @@ class RequestsMock:
     POST: Literal["POST"] = "POST"
     PUT: Literal["PUT"] = "PUT"
 
+    Response: Type[Response] = Response
+
+    # Make the `matchers` name available under a RequestsMock instance
+    from responses import matchers
+
     response_callback: Optional[Callable[[Any], Any]] = None
 
     def __init__(


Test patch:
diff --git a/responses/tests/test_matchers.py b/responses/tests/test_matchers.py
index 2775fff..6f5f7fb 100644
--- a/responses/tests/test_matchers.py
+++ b/responses/tests/test_matchers.py
@@ -844,6 +844,29 @@ def test_fragment_identifier_matcher_and_match_querystring():
     assert_reset()
 
 
+def test_matchers_under_requests_mock_object():
+    def run():
+        # ensure all access to responses or matchers is only going
+        # through the RequestsMock instance in the context manager
+        responses = None  # noqa: F841
+        matchers = None  # noqa: F841
+        from responses import RequestsMock
+
+        with RequestsMock(assert_all_requests_are_fired=True) as rsps:
+            url = "http://example.com"
+            rsps.add(
+                rsps.GET,
+                url,
+                body=b"test",
+                match=[rsps.matchers.body_matcher("123456")],
+            )
+            resp = requests.get("http://example.com", data="123456")
+            assert_response(resp, "test")
+
+    run()
+    assert_reset()
+
+
 class TestHeaderWithRegex:
     @property
     def url(self):  # type: ignore[misc]
diff --git a/responses/tests/test_responses.py b/responses/tests/test_responses.py
index 012eb90..c275009 100644
--- a/responses/tests/test_responses.py
+++ b/responses/tests/test_responses.py
@@ -108,6 +108,29 @@ def test_response_with_instance():
     assert_reset()
 
 
+def test_response_with_instance_under_requests_mock_object():
+    def run():
+        # ensure all access to responses is only going through
+        # the RequestsMock instance in the context manager
+        responses = None  # noqa: F841
+        from responses import RequestsMock
+
+        with RequestsMock(assert_all_requests_are_fired=True) as rsps:
+            rsps.add(rsps.Response(method=rsps.GET, url="http://example.com"))
+            resp = requests.get("http://example.com")
+            assert_response(resp, "")
+            assert len(rsps.calls) == 1
+            assert rsps.calls[0].request.url == "http://example.com/"
+
+            resp = requests.get("http://example.com?foo=bar")
+            assert_response(resp, "")
+            assert len(rsps.calls) == 2
+            assert rsps.calls[1].request.url == "http://example.com/?foo=bar"
+
+    run()
+    assert_reset()
+
+
 @pytest.mark.parametrize(
     "original,replacement",
     [