Repository: getmoto/moto

Issue:
Responses as context manager gets overridden if Moto context managers are set up afterwards
If both Moto and responses are set up as context managers, if responses is set up prior to Moto, then any mocked responses from the requests library will be dropped.

Python version: 3.9.16

```text
moto==4.1.11
responses==0.23.1
boto3==1.26.153
requests==2.31.0
```

```python
import boto3
import moto
import pytest
import requests
import responses

url = "https://www.example1234567890.com"


@pytest.fixture(autouse=True)
def aws_credentials(monkeypatch):
    """Mocked AWS Credentials for moto."""

    monkeypatch.setenv("AWS_ACCESS_KEY_ID", "testing")
    monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "testing")
    monkeypatch.setenv("AWS_SECURITY_TOKEN", "testing")
    monkeypatch.setenv("AWS_SESSION_TOKEN", "testing")


def code_under_test():
    client = boto3.resource("s3")
    bucket = client.Bucket("mybucket")
    bucket.Object("my_file.json").get()["Body"].read()
    data = requests.get(url).json()
    assert data == {"foo": "bar"}


@responses.activate
@moto.mock_s3
def test_with_decorators():
    client = boto3.client("s3")
    client.create_bucket(Bucket="mybucket")
    client.put_object(Bucket="mybucket", Key="my_file.json", Body=b"{}")
    responses.add(responses.GET, url, json={"foo": "bar"})

    code_under_test()


@moto.mock_s3
@responses.activate
def test_with_decorators_alternate():
    client = boto3.client("s3")
    client.create_bucket(Bucket="mybucket")
    client.put_object(Bucket="mybucket", Key="my_file.json", Body=b"{}")
    responses.add(responses.GET, url, json={"foo": "bar"})

    code_under_test()


def test_with_context_managers():
    with moto.mock_s3():
        with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
            client = boto3.client("s3")
            client.create_bucket(Bucket="mybucket")
            client.put_object(Bucket="mybucket", Key="my_file.json", Body=b"{}")
            rsps.add(responses.GET, url, json={"foo": "bar"})

            code_under_test()


# When responses is run as a context manager first, alongside moto as a context manager,
# the mocked requests get overriden.
def test_with_context_managers_alternate():
    with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
        with moto.mock_s3():
            client = boto3.client("s3")
            client.create_bucket(Bucket="mybucket")
            client.put_object(Bucket="mybucket", Key="my_file.json", Body=b"{}")
            rsps.add(responses.GET, url, json={"foo": "bar"})

            code_under_test()
```

This is closely related to #4870 



Discussion:
Hi @Jasonca2, thanks for raising this. I'm not sure whether this is something that Moto can fix - it looks like that's just how `responses` works. 

I've raised an issue with them to see if/how this can be solved:  https://github.com/getsentry/responses/issues/645
@Jasonca2 
`responses` maintainer here

yes, from the code that you supplied it will not work together nicely. Since `requests` adapter will not know what to do and which mocks to call.

@bblommers 
I am not sure how `moto` is structured. But can you maybe expose `responses` registry to the user? Then it should work. 
like 

```python
with moto.mock_s3() as m1:
    m1.responses_registry.add(Response())
```

or did you completely override the `RequestMock` object ?
@beliaev-maksim We create a new RequestsMock object here:

https://github.com/getmoto/moto/blob/af4a8e2ba6faf0c95d836f8cb66d73d80bd6250b/moto/core/custom_responses_mock.py#L156

That is exposed as `moto.core.models.responses_mock`, so it could be used. The downside of that approach is that we configure a custom registry, and with `assert_all_requests_are_fired=False`, and I imagine that many users will want/need a different configuration for their own mock-requests.
yea, entirely different registry is not gonna play nicely 

what I do not understand is does this test work? and only reversed order fails ?

or all tests fail?

```python
def test_with_context_managers():
    with moto.mock_s3():
        with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
            client = boto3.client("s3")
            client.create_bucket(Bucket="mybucket")
            client.put_object(Bucket="mybucket", Key="my_file.json", Body=b"{}")
            rsps.add(responses.GET, url, json={"foo": "bar"})

            code_under_test()
```

@bblommers 
another idea, a bit wild but I do it in `responses` for some testing purposes

if you have two nested decorators, the problem is that you reach one decorator which does not have registered responses.

you need to allow all the traffic (expect essential for `moto`) and pass through it

I opened an issue https://github.com/getmoto/moto/issues/6433

then user defined Mock will catch it
> what I do not understand is does this test work? and only reversed order fails ?
> 
That breaks the other way actually. URL's defined by the user are intercepted, but not Moto's URLs.

> you need to allow all the traffic (expect essential for moto) and pass through it
>

I understand why it could work.. but it doesn't behave as I would expect.
With the `test_with_context_managers_alternate`, and matching the exact URL (`add_passthru("https://www.example1234567890.com")`, the URL is not intercepted by `responses` - I get a ConnectionError because the website does not exist.
@bblommers and passthru is registered inside `moto` ?

the idea behind is that moto will pass requests as regular request, but on the second level it will be caught by regular `responses`

for test purpose you can try to pass all the data: `add_passthru(re.compile(".*"))`
@beliaev-maksim This is the equivalent stand-alone test case (so without Moto), I believe?
```
def test_with_context_managers_alternate():
    import re
    with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:

        moto_mock = responses.RequestsMock(assert_all_requests_are_fired=False)
        moto_mock.add_passthru(url)
        moto_mock.start()
        rsps.add(responses.GET, url, json={"foo": "bar"})

        data = requests.get(url).json()
        assert data == {"foo": "bar"}

        moto_mock.stop()
```
Still the `ConnectionError`. Changing the passthrough to `re.compile(".*")` doesn't work either
@bblommers
Right, just ran your example in debugger

I have some idea, but will require "responses" update

Still researching 
@beliaev-maksim & @bblommers 
Thanks for looking into this. 
Do you have any updates on this, @beliaev-maksim? I saw the `unbound_send` PR you created, but I'm not familiar enough with the inner workings of `responses` to see if/how that can be used here.
@bblommers 
oh, sorry for that, got busy with my main work :D
thanks for the reminder.

yes, with this PR merged it should be possible to do following:

```python
import responses
import requests


def test_with_context_managers_alternate():
    url = "https://www.example1234567890.com"
    moto_url = "https://www.moto.com"
    with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:

        moto_mock = responses.RequestsMock(assert_all_requests_are_fired=False)
        moto_mock.start()
        moto_mock.add_passthru(url)

        responses._real_send = rsps.unbound_on_send()

        moto_mock.add(responses.GET, moto_url, json={"moto": "mock"})
        rsps.add(responses.GET, url, json={"foo": "bar"})

        data = requests.get(url).json()
        assert data == {"foo": "bar"}

        data1 = requests.get(moto_url).json()
        assert data1 == {"moto": "mock"}

        moto_mock.stop()
```

will that snippet be sufficient for you to proceed with updating docs/code for moto?
basically, the idea is that now we overwrite the value of real send and next time when moto tries to send a real request it will hit the `rsps` decorator.

if that is not sufficient, can you please provide another simplified code snippet that I can play and debug
Ah, gotcha - thanks for the snippet @beliaev-maksim. We don't have access to `rsps` within Moto, so that's not something that can be fixed in our codebase. But just adding the `responses._real_send = rsps.unbound_on_send()` fixes the problem for the OP.

@Jasonca2 Is this an acceptable workaround for you? All the tests from your example pass, by just adding that one line.
This fix doesn't seem to work in every case. I am getting some recursion errors with passthroughs, here: https://github.com/getsentry/responses/issues/669#issuecomment-1725133874

Resolving patch:
diff --git a/moto/core/models.py b/moto/core/models.py
--- a/moto/core/models.py
+++ b/moto/core/models.py
@@ -302,6 +302,23 @@ def patch_resource(resource: Any) -> None:
         raise Exception(f"Argument {resource} should be of type boto3.resource")
 
 
+def override_responses_real_send(user_mock: Optional[responses.RequestsMock]) -> None:
+    """
+    Moto creates it's own Responses-object responsible for intercepting AWS requests
+    If a custom Responses-object is created by the user, Moto will hijack any of the pass-thru's set
+
+    Call this method to ensure any requests unknown to Moto are passed through the custom Responses-object.
+
+    Set the user_mock argument to None to reset this behaviour.
+
+    Note that this is only supported from Responses>=0.24.0
+    """
+    if user_mock is None:
+        responses_mock._real_send = responses._real_send
+    else:
+        responses_mock._real_send = user_mock.unbound_on_send()
+
+
 class BotocoreEventMockAWS(BaseMockAWS):
     def reset(self) -> None:
         botocore_stubber.reset()


Test patch:
diff --git a/tests/test_core/test_responses_module.py b/tests/test_core/test_responses_module.py
--- a/tests/test_core/test_responses_module.py
+++ b/tests/test_core/test_responses_module.py
@@ -5,18 +5,21 @@
 import boto3
 import requests
 import responses
-from moto import mock_s3, settings
+from moto import mock_dynamodb, mock_s3, settings
+from moto.core.models import override_responses_real_send
+from moto.core.versions import RESPONSES_VERSION
+from moto.utilities.distutils_version import LooseVersion
 from unittest import SkipTest, TestCase
 
 
 class TestResponsesModule(TestCase):
-    def setUp(self):
+    def setUp(self) -> None:
         if settings.TEST_SERVER_MODE:
             raise SkipTest("No point in testing responses-decorator in ServerMode")
 
     @mock_s3
     @responses.activate
-    def test_moto_first(self):
+    def test_moto_first(self) -> None:
         """
         Verify we can activate a user-defined `responses` on top of our Moto mocks
         """
@@ -24,13 +27,13 @@ def test_moto_first(self):
 
     @responses.activate
     @mock_s3
-    def test_moto_second(self):
+    def test_moto_second(self) -> None:
         """
         Verify we can load Moto after activating a `responses`-mock
         """
         self.moto_responses_compatibility()
 
-    def moto_responses_compatibility(self):
+    def moto_responses_compatibility(self) -> None:
         responses.add(
             responses.GET, url="http://127.0.0.1/lkdsfjlkdsa", json={"a": "4"}
         )
@@ -42,7 +45,7 @@ def moto_responses_compatibility(self):
             assert r.json() == {"a": "4"}
 
     @responses.activate
-    def test_moto_as_late_as_possible(self):
+    def test_moto_as_late_as_possible(self) -> None:
         """
         Verify we can load moto after registering a response
         """
@@ -60,3 +63,47 @@ def test_moto_as_late_as_possible(self):
         # And outside of Moto
         with requests.get("http://127.0.0.1/lkdsfjlkdsa") as r:
             assert r.json() == {"a": "4"}
+
+
+@mock_dynamodb
+class TestResponsesMockWithPassThru(TestCase):
+    """
+    https://github.com/getmoto/moto/issues/6417
+    """
+
+    def setUp(self) -> None:
+        if RESPONSES_VERSION < LooseVersion("0.24.0"):
+            raise SkipTest("Can only test this with responses >= 0.24.0")
+
+        self.r_mock = responses.RequestsMock(assert_all_requests_are_fired=True)
+        override_responses_real_send(self.r_mock)
+        self.r_mock.start()
+        self.r_mock.add_passthru("http://ip.jsontest.com")
+
+    def tearDown(self) -> None:
+        self.r_mock.stop()
+        self.r_mock.reset()
+        override_responses_real_send(None)
+
+    def http_requests(self) -> str:
+        # Mock this website
+        requests.post("https://example.org")
+
+        # Passthrough this website
+        assert requests.get("http://ip.jsontest.com").status_code == 200
+
+        return "OK"
+
+    def aws_and_http_requests(self) -> str:
+        ddb = boto3.client("dynamodb", "us-east-1")
+        assert ddb.list_tables()["TableNames"] == []
+        self.http_requests()
+        return "OK"
+
+    def test_http_requests(self) -> None:
+        self.r_mock.add(responses.POST, "https://example.org", status=200)
+        self.assertEqual("OK", self.http_requests())
+
+    def test_aws_and_http_requests(self) -> None:
+        self.r_mock.add(responses.POST, "https://example.org", status=200)
+        self.assertEqual("OK", self.aws_and_http_requests())