Repository: ag2ai/faststream

Issue:
Bug: starting the broker breaks in-memory behaviour
**Describe the bug**
I'm not really sure if this is a bug or not. If it's not a bug but an intended behaviour, then I suppose it should be at least mentioned in the docs.

Right now, if you start the broker and then try to patch it with the in-memory broker for testing purposes, call count assertions will fail (at least when testing a publisher).

It may seem obvious to someone, but I've spent quite some time trying to figure out the problem. Connecting a broker before running tests didn't seem like the problem: what if I wish to run tests both in-memory and with a real thing, so why not making a connection beforehand?

**How to reproduce**
Consider this example code:

```python
from collections.abc import AsyncIterator

import pytest
from dishka import Provider, provide, Scope, AsyncContainer, make_async_container
from faststream.rabbit import TestRabbitBroker, RabbitBroker
from faststream.rabbit.publisher.asyncapi import AsyncAPIPublisher


class RabbitProvider(Provider):
    @provide(scope=Scope.APP)
    async def broker(self) -> AsyncIterator[RabbitBroker]:
        dsn = "amqp://guest:guest@0.0.0.0:5672/"
        broker = RabbitBroker(url=dsn)

        await broker.start()
        yield broker
        await broker.close()

    @provide(scope=Scope.APP)
    async def publisher(self, broker: RabbitBroker) -> AsyncAPIPublisher:
        return broker.publisher()


@pytest.fixture
async def container() -> AsyncIterator[AsyncContainer]:
    container = make_async_container(RabbitProvider())
    yield container
    await container.close()


async def test_publisher(container: AsyncContainer):
    broker = await container.get(RabbitBroker)
    publisher = await container.get(AsyncAPIPublisher)

    async with TestRabbitBroker(broker):
        for i in range(10):
            await publisher.publish(f"message {i}")

        assert publisher.mock.call_count == 10
```

Running this test would lead to `AssertionError: assert 0 == 10`. However, if you remove `await broker.start()` call from the broker provider method, it works fine.

**Expected behavior**
In-memory broker works even if the broker has established the connection.

**Observed behavior**
In-memory broker doesn't register `publish` calls if it has established the connection before patching.

**Environment**
`Running FastStream 0.5.7 with CPython 3.12.0 on Darwin`



Discussion:
I am planning a big TestClient refactor, but now we should complete all production usecases to move forward, sorry, I add it to backlog (and big thanks for the report)

Btw, why are you not in the RU telegram group yet?
No worries, I don't expect every issue to be fixed in hours. :)

> Btw, why are you not in the RU telegram group yet?

I enjoy reporting issues.




Patch:
diff --git a/.codespell-whitelist.txt b/.codespell-whitelist.txt
index 0ec54623a2..46237f7f3d 100644
--- a/.codespell-whitelist.txt
+++ b/.codespell-whitelist.txt
@@ -1,3 +1,3 @@
 dependant
 unsecure
-socio-economic
\ No newline at end of file
+socio-economic
diff --git a/.github/workflows/docs_update-references.yaml b/.github/workflows/docs_update-references.yaml
index 665f1bec99..f119ec2253 100644
--- a/.github/workflows/docs_update-references.yaml
+++ b/.github/workflows/docs_update-references.yaml
@@ -30,7 +30,12 @@ jobs:
           cache-dependency-path: pyproject.toml
       - name: Install Dependencies
         if: steps.cache.outputs.cache-hit != 'true'
-        run: pip install -e ".[dev]"
+        shell: bash
+        # should install with `-e`
+        run: |
+          set -ux
+          python -m pip install uv
+          uv pip install --system -e ".[dev]"
       - name: Run build docs
         run: bash scripts/build-docs.sh
       - name: Commit
diff --git a/.secrets.baseline b/.secrets.baseline
index 3fd4156bb0..81e8e12bdc 100644
--- a/.secrets.baseline
+++ b/.secrets.baseline
@@ -128,7 +128,7 @@
         "filename": "docs/docs/en/release.md",
         "hashed_secret": "35675e68f4b5af7b995d9205ad0fc43842f16450",
         "is_verified": false,
-        "line_number": 1325,
+        "line_number": 1423,
         "is_secret": false
       }
     ],
@@ -163,5 +163,5 @@
       }
     ]
   },
-  "generated_at": "2024-06-10T09:56:52Z"
+  "generated_at": "2024-07-23T21:38:30Z"
 }
diff --git a/docs/docs/en/kafka/Subscriber/index.md b/docs/docs/en/kafka/Subscriber/index.md
index f40c9a20ca..831acc814b 100644
--- a/docs/docs/en/kafka/Subscriber/index.md
+++ b/docs/docs/en/kafka/Subscriber/index.md
@@ -71,4 +71,4 @@ async def base_handler(
     level: str = Path(),
 ):
     ...
-```
\ No newline at end of file
+```
diff --git a/faststream/nats/subscriber/usecase.py b/faststream/nats/subscriber/usecase.py
index bed0332586..4370f70e1a 100644
--- a/faststream/nats/subscriber/usecase.py
+++ b/faststream/nats/subscriber/usecase.py
@@ -426,6 +426,9 @@ async def _create_subscription(  # type: ignore[override]
         connection: "Client",
     ) -> None:
         """Create NATS subscription and start consume task."""
+        if self.subscription:
+            return
+
         self.subscription = await connection.subscribe(
             subject=self.clear_subject,
             queue=self.queue,
@@ -495,6 +498,9 @@ async def _create_subscription(  # type: ignore[override]
         connection: "Client",
     ) -> None:
         """Create NATS subscription and start consume task."""
+        if self.subscription:
+            return
+
         self.start_consume_task()
 
         self.subscription = await connection.subscribe(
@@ -576,6 +582,9 @@ async def _create_subscription(  # type: ignore[override]
         connection: "JetStreamContext",
     ) -> None:
         """Create NATS subscription and start consume task."""
+        if self.subscription:
+            return
+
         self.subscription = await connection.subscribe(
             subject=self.clear_subject,
             queue=self.queue,
@@ -636,6 +645,9 @@ async def _create_subscription(  # type: ignore[override]
         connection: "JetStreamContext",
     ) -> None:
         """Create NATS subscription and start consume task."""
+        if self.subscription:
+            return
+
         self.start_consume_task()
 
         self.subscription = await connection.subscribe(
@@ -698,6 +710,9 @@ async def _create_subscription(  # type: ignore[override]
         connection: "JetStreamContext",
     ) -> None:
         """Create NATS subscription and start consume task."""
+        if self.subscription:
+            return
+
         self.subscription = await connection.pull_subscribe(
             subject=self.clear_subject,
             config=self.config,
@@ -775,6 +790,9 @@ async def _create_subscription(  # type: ignore[override]
         connection: "JetStreamContext",
     ) -> None:
         """Create NATS subscription and start consume task."""
+        if self.subscription:
+            return
+
         self.start_consume_task()
 
         self.subscription = await connection.pull_subscribe(
@@ -841,6 +859,9 @@ async def _create_subscription(  # type: ignore[override]
         connection: "JetStreamContext",
     ) -> None:
         """Create NATS subscription and start consume task."""
+        if self.subscription:
+            return
+
         self.subscription = await connection.pull_subscribe(
             subject=self.clear_subject,
             config=self.config,
@@ -905,6 +926,9 @@ async def _create_subscription(  # type: ignore[override]
         *,
         connection: "KVBucketDeclarer",
     ) -> None:
+        if self.subscription:
+            return
+
         bucket = await connection.create_key_value(
             bucket=self.kv_watch.name,
             declare=self.kv_watch.declare,
@@ -1012,6 +1036,9 @@ async def _create_subscription(  # type: ignore[override]
         *,
         connection: "OSBucketDeclarer",
     ) -> None:
+        if self.subscription:
+            return
+
         self.bucket = await connection.create_object_store(
             bucket=self.subject,
             declare=self.obj_watch.declare,
diff --git a/faststream/redis/subscriber/usecase.py b/faststream/redis/subscriber/usecase.py
index 9b5185afea..67325098be 100644
--- a/faststream/redis/subscriber/usecase.py
+++ b/faststream/redis/subscriber/usecase.py
@@ -151,10 +151,15 @@ async def start(  # type: ignore[override]
         self,
         *args: Any,
     ) -> None:
+        if self.task:
+            return
+
         await super().start()
 
         start_signal = anyio.Event()
-        self.task = asyncio.create_task(self._consume(*args, start_signal=start_signal))
+        self.task = asyncio.create_task(
+            self._consume(*args, start_signal=start_signal)
+        )
 
         with anyio.fail_after(3.0):
             await start_signal.wait()
@@ -253,6 +258,9 @@ def get_log_context(
 
     @override
     async def start(self) -> None:
+        if self.subscription:
+            return
+
         assert self._client, "You should setup subscriber at first."  # nosec B101
 
         self.subscription = psub = self._client.pubsub()
@@ -352,6 +360,9 @@ async def _consume(  # type: ignore[override]
 
     @override
     async def start(self) -> None:
+        if self.task:
+            return
+
         assert self._client, "You should setup subscriber at first."  # nosec B101
         await super().start(self._client)
 
@@ -512,7 +523,11 @@ def get_log_context(
 
     @override
     async def start(self) -> None:
+        if self.task:
+            return
+
         assert self._client, "You should setup subscriber at first."  # nosec B101
+
         client = self._client
 
         self.extra_watcher_options.update(


Test patch:
diff --git a/.github/workflows/pr_tests.yaml b/.github/workflows/pr_tests.yaml
index 39d033420a..785c19c690 100644
--- a/.github/workflows/pr_tests.yaml
+++ b/.github/workflows/pr_tests.yaml
@@ -31,7 +31,7 @@ jobs:
         run: |
           set -ux
           python -m pip install uv
-          uv pip install --system -e ".[lint]"
+          uv pip install --system ".[lint]"
 
       - name: Run ruff
         shell: bash
diff --git a/faststream/rabbit/testing.py b/faststream/rabbit/testing.py
index 3d3a274418..ec57d7a17d 100644
--- a/faststream/rabbit/testing.py
+++ b/faststream/rabbit/testing.py
@@ -1,4 +1,6 @@
-from typing import TYPE_CHECKING, Any, Optional, Union
+from contextlib import contextmanager
+from typing import TYPE_CHECKING, Any, Generator, Optional, Union
+from unittest import mock
 from unittest.mock import AsyncMock
 
 import aiormq
@@ -34,10 +36,18 @@ class TestRabbitBroker(TestBroker[RabbitBroker]):
     """A class to test RabbitMQ brokers."""
 
     @classmethod
-    def _patch_test_broker(cls, broker: RabbitBroker) -> None:
-        broker._channel = AsyncMock()
-        broker.declarer = AsyncMock()
-        super()._patch_test_broker(broker)
+    @contextmanager
+    def _patch_broker(cls, broker: RabbitBroker) -> Generator[None, None, None]:
+        with mock.patch.object(
+            broker,
+            "_channel",
+            new_callable=AsyncMock,
+        ), mock.patch.object(
+            broker,
+            "declarer",
+            new_callable=AsyncMock,
+        ), super()._patch_broker(broker):
+            yield
 
     @staticmethod
     async def _fake_connect(broker: RabbitBroker, *args: Any, **kwargs: Any) -> None:
diff --git a/faststream/testing/broker.py b/faststream/testing/broker.py
index 208873eeb0..07ee03dee7 100644
--- a/faststream/testing/broker.py
+++ b/faststream/testing/broker.py
@@ -1,19 +1,20 @@
 import warnings
 from abc import abstractmethod
-from contextlib import asynccontextmanager
+from contextlib import asynccontextmanager, contextmanager
 from functools import partial
-from types import MethodType
 from typing import (
     TYPE_CHECKING,
     Any,
     AsyncGenerator,
+    Generator,
     Generic,
     Optional,
     Tuple,
     Type,
     TypeVar,
 )
-from unittest.mock import AsyncMock, MagicMock
+from unittest import mock
+from unittest.mock import MagicMock
 
 from faststream.broker.core.usecase import BrokerUsecase
 from faststream.broker.message import StreamMessage, decode_message, encode_message
@@ -21,7 +22,7 @@
 from faststream.broker.wrapper.call import HandlerCallWrapper
 from faststream.testing.app import TestApp
 from faststream.utils.ast import is_contains_context_name
-from faststream.utils.functions import timeout_scope
+from faststream.utils.functions import sync_fake_context, timeout_scope
 
 if TYPE_CHECKING:
     from types import TracebackType
@@ -29,7 +30,6 @@
     from faststream.broker.subscriber.proto import SubscriberProto
     from faststream.broker.types import BrokerMiddleware
 
-
 Broker = TypeVar("Broker", bound=BrokerUsecase[Any, Any])
 
 
@@ -113,22 +113,43 @@ async def __aexit__(self, *args: Any) -> None:
     async def _create_ctx(self) -> AsyncGenerator[Broker, None]:
         if self.with_real:
             self._fake_start(self.broker)
+            context = sync_fake_context()
         else:
-            self._patch_test_broker(self.broker)
+            context = self._patch_broker(self.broker)
 
-        async with self.broker:
-            try:
-                if not self.connect_only:
-                    await self.broker.start()
-                yield self.broker
-            finally:
-                self._fake_close(self.broker)
+        with context:
+            async with self.broker:
+                try:
+                    if not self.connect_only:
+                        await self.broker.start()
+                    yield self.broker
+                finally:
+                    self._fake_close(self.broker)
 
     @classmethod
-    def _patch_test_broker(cls, broker: Broker) -> None:
-        broker.start = AsyncMock(wraps=partial(cls._fake_start, broker))  # type: ignore[method-assign]
-        broker._connect = MethodType(cls._fake_connect, broker)  # type: ignore[method-assign]
-        broker.close = AsyncMock()  # type: ignore[method-assign]
+    @contextmanager
+    def _patch_broker(cls, broker: Broker) -> Generator[None, None, None]:
+        with mock.patch.object(
+            broker,
+            "start",
+            wraps=partial(cls._fake_start, broker),
+        ), mock.patch.object(
+            broker,
+            "_connect",
+            wraps=partial(cls._fake_connect, broker),
+        ), mock.patch.object(
+            broker,
+            "close",
+        ), mock.patch.object(
+            broker,
+            "_connection",
+            new=None,
+        ), mock.patch.object(
+            broker,
+            "_producer",
+            new=None,
+        ):
+            yield
 
     @classmethod
     def _fake_start(cls, broker: Broker, *args: Any, **kwargs: Any) -> None:
diff --git a/tests/brokers/base/connection.py b/tests/brokers/base/connection.py
index 3307698f61..4c73999497 100644
--- a/tests/brokers/base/connection.py
+++ b/tests/brokers/base/connection.py
@@ -53,5 +53,5 @@ async def test_ping_timeout(self, settings):
         kwargs = self.get_broker_args(settings)
         broker = self.broker("wrong_url")
         await broker.connect(**kwargs)
-        assert not await broker.ping(timeout=0.00001)
+        assert not await broker.ping(timeout=1e-24)
         await broker.close()
diff --git a/tests/brokers/base/testclient.py b/tests/brokers/base/testclient.py
index 8381c95dc1..9c454ce3cd 100644
--- a/tests/brokers/base/testclient.py
+++ b/tests/brokers/base/testclient.py
@@ -1,3 +1,8 @@
+import asyncio
+from abc import abstractmethod
+from unittest.mock import Mock
+
+import anyio
 import pytest
 
 from faststream.testing.broker import TestBroker
@@ -15,6 +20,10 @@ class BrokerTestclientTestcase(
     build_message: AnyCallable
     test_class: TestBroker
 
+    @abstractmethod
+    def get_fake_producer_class(self) -> type:
+        raise NotImplementedError
+
     @pytest.mark.asyncio()
     async def test_subscriber_mock(self, queue: str):
         test_broker = self.get_broker()
@@ -72,3 +81,55 @@ async def m(msg):  # pragma: no cover
 
             with pytest.raises(ValueError):  # noqa: PT011
                 await test_broker.publish("hello", queue)
+
+    async def test_broker_gets_patched_attrs_within_cm(self):
+        test_broker = self.get_broker()
+        fake_producer_class = self.get_fake_producer_class()
+        await test_broker.start()
+
+        async with self.test_class(test_broker) as br:
+            assert isinstance(br.start, Mock)
+            assert isinstance(br._connect, Mock)
+            assert isinstance(br.close, Mock)
+            assert isinstance(br._producer, fake_producer_class)
+
+        assert not isinstance(br.start, Mock)
+        assert not isinstance(br._connect, Mock)
+        assert not isinstance(br.close, Mock)
+        assert br._connection is not None
+        assert not isinstance(br._producer, fake_producer_class)
+
+    async def test_broker_with_real_doesnt_get_patched(self):
+        test_broker = self.get_broker()
+        await test_broker.start()
+
+        async with self.test_class(test_broker, with_real=True) as br:
+            assert not isinstance(br.start, Mock)
+            assert not isinstance(br._connect, Mock)
+            assert not isinstance(br.close, Mock)
+            assert br._connection is not None
+            assert br._producer is not None
+
+    async def test_broker_with_real_patches_subscribers_and_subscribers(
+        self, queue: str
+    ):
+        test_broker = self.get_broker()
+
+        publisher = test_broker.publisher(f"{queue}1")
+
+        @test_broker.subscriber(queue)
+        async def m(msg):
+            await publisher.publish(f"response: {msg}")
+
+        await test_broker.start()
+
+        async with self.test_class(test_broker, with_real=True) as br:
+            await br.publish("hello", queue)
+
+            await m.wait_call(3.0)
+
+            m.mock.assert_called_once_with("hello")
+            with anyio.fail_after(3.0):
+                while not publisher.mock.called:
+                    await asyncio.sleep(0.1)
+                publisher.mock.assert_called_once_with("response: hello")
diff --git a/tests/brokers/confluent/test_test_client.py b/tests/brokers/confluent/test_test_client.py
index 53c10b7d20..43c558a7de 100644
--- a/tests/brokers/confluent/test_test_client.py
+++ b/tests/brokers/confluent/test_test_client.py
@@ -4,6 +4,7 @@
 
 from faststream import BaseMiddleware
 from faststream.confluent import KafkaBroker, TestKafkaBroker
+from faststream.confluent.testing import FakeProducer
 from tests.brokers.base.testclient import BrokerTestclientTestcase
 
 
@@ -19,6 +20,9 @@ def get_broker(self, apply_types: bool = False):
     def patch_broker(self, broker: KafkaBroker) -> TestKafkaBroker:
         return TestKafkaBroker(broker)
 
+    def get_fake_producer_class(self) -> type:
+        return FakeProducer
+
     @pytest.mark.confluent()
     async def test_with_real_testclient(
         self,
@@ -202,3 +206,17 @@ async def subscriber2(): ...
 
         assert subscriber1.mock.call_count == 1
         assert subscriber2.mock.call_count == 0
+
+    @pytest.mark.confluent()
+    async def test_broker_gets_patched_attrs_within_cm(self):
+        await super().test_broker_gets_patched_attrs_within_cm()
+
+    @pytest.mark.confluent()
+    async def test_broker_with_real_doesnt_get_patched(self):
+        await super().test_broker_with_real_doesnt_get_patched()
+
+    @pytest.mark.confluent()
+    async def test_broker_with_real_patches_subscribers_and_subscribers(
+        self, queue: str
+    ):
+        await super().test_broker_with_real_patches_subscribers_and_subscribers(queue)
diff --git a/tests/brokers/kafka/test_test_client.py b/tests/brokers/kafka/test_test_client.py
index b1c01b8ff0..1d96940310 100644
--- a/tests/brokers/kafka/test_test_client.py
+++ b/tests/brokers/kafka/test_test_client.py
@@ -4,6 +4,7 @@
 
 from faststream import BaseMiddleware
 from faststream.kafka import KafkaBroker, TestKafkaBroker, TopicPartition
+from faststream.kafka.testing import FakeProducer
 from tests.brokers.base.testclient import BrokerTestclientTestcase
 
 
@@ -17,6 +18,9 @@ def get_broker(self, apply_types: bool = False):
     def patch_broker(self, broker: KafkaBroker) -> TestKafkaBroker:
         return TestKafkaBroker(broker)
 
+    def get_fake_producer_class(self) -> type:
+        return FakeProducer
+
     async def test_partition_match(
         self,
         queue: str,
@@ -250,3 +254,17 @@ async def subscriber2(): ...
 
         assert subscriber1.mock.call_count == 1
         assert subscriber2.mock.call_count == 0
+
+    @pytest.mark.kafka()
+    async def test_broker_gets_patched_attrs_within_cm(self):
+        await super().test_broker_gets_patched_attrs_within_cm()
+
+    @pytest.mark.kafka()
+    async def test_broker_with_real_doesnt_get_patched(self):
+        await super().test_broker_with_real_doesnt_get_patched()
+
+    @pytest.mark.kafka()
+    async def test_broker_with_real_patches_subscribers_and_subscribers(
+        self, queue: str
+    ):
+        await super().test_broker_with_real_patches_subscribers_and_subscribers(queue)
diff --git a/tests/brokers/nats/test_router.py b/tests/brokers/nats/test_router.py
index c6c2b60bae..8ca77606e1 100644
--- a/tests/brokers/nats/test_router.py
+++ b/tests/brokers/nats/test_router.py
@@ -137,7 +137,7 @@ class TestRouterLocal(RouterLocalTestcase):
     route_class = NatsRoute
     publisher_class = NatsPublisher
 
-    def test_include_stream(
+    async def test_include_stream(
         self,
         router: NatsRouter,
         pub_broker: NatsBroker,
diff --git a/tests/brokers/nats/test_test_client.py b/tests/brokers/nats/test_test_client.py
index e24cbb577e..cf801a9362 100644
--- a/tests/brokers/nats/test_test_client.py
+++ b/tests/brokers/nats/test_test_client.py
@@ -5,6 +5,7 @@
 from faststream import BaseMiddleware
 from faststream.exceptions import SetupError
 from faststream.nats import ConsumerConfig, JStream, NatsBroker, PullSub, TestNatsBroker
+from faststream.nats.testing import FakeProducer
 from tests.brokers.base.testclient import BrokerTestclientTestcase
 
 
@@ -18,6 +19,9 @@ def get_broker(self, apply_types: bool = False) -> NatsBroker:
     def patch_broker(self, broker: NatsBroker) -> TestNatsBroker:
         return TestNatsBroker(broker)
 
+    def get_fake_producer_class(self) -> type:
+        return FakeProducer
+
     @pytest.mark.asyncio()
     async def test_stream_publish(
         self,
@@ -251,3 +255,17 @@ def subscriber(m):
             await br.publish(1, f"{queue}.b")
             await br.publish(2, f"{queue}.a")
             subscriber.mock.assert_called_once_with(2)
+
+    @pytest.mark.nats()
+    async def test_broker_gets_patched_attrs_within_cm(self):
+        await super().test_broker_gets_patched_attrs_within_cm()
+
+    @pytest.mark.nats()
+    async def test_broker_with_real_doesnt_get_patched(self):
+        await super().test_broker_with_real_doesnt_get_patched()
+
+    @pytest.mark.nats()
+    async def test_broker_with_real_patches_subscribers_and_subscribers(
+        self, queue: str
+    ):
+        await super().test_broker_with_real_patches_subscribers_and_subscribers(queue)
diff --git a/tests/brokers/rabbit/test_test_client.py b/tests/brokers/rabbit/test_test_client.py
index dde1383ff4..9f46002176 100644
--- a/tests/brokers/rabbit/test_test_client.py
+++ b/tests/brokers/rabbit/test_test_client.py
@@ -12,7 +12,7 @@
     TestRabbitBroker,
 )
 from faststream.rabbit.annotations import RabbitMessage
-from faststream.rabbit.testing import apply_pattern
+from faststream.rabbit.testing import FakeProducer, apply_pattern
 from tests.brokers.base.testclient import BrokerTestclientTestcase
 
 
@@ -26,6 +26,9 @@ def get_broker(self, apply_types: bool = False) -> RabbitBroker:
     def patch_broker(self, broker: RabbitBroker) -> RabbitBroker:
         return TestRabbitBroker(broker)
 
+    def get_fake_producer_class(self) -> type:
+        return FakeProducer
+
     async def test_rpc_conflicts_reply(self, queue):
         broker = self.get_broker()
 
@@ -293,6 +296,20 @@ async def h2(msg): ...
 
         assert len(routes) == 2
 
+    @pytest.mark.rabbit()
+    async def test_broker_gets_patched_attrs_within_cm(self):
+        await super().test_broker_gets_patched_attrs_within_cm()
+
+    @pytest.mark.rabbit()
+    async def test_broker_with_real_doesnt_get_patched(self):
+        await super().test_broker_with_real_doesnt_get_patched()
+
+    @pytest.mark.rabbit()
+    async def test_broker_with_real_patches_subscribers_and_subscribers(
+        self, queue: str
+    ):
+        await super().test_broker_with_real_patches_subscribers_and_subscribers(queue)
+
 
 @pytest.mark.parametrize(
     ("pattern", "current", "result"),
diff --git a/tests/brokers/redis/test_test_client.py b/tests/brokers/redis/test_test_client.py
index ae6340ad7a..80f00ca441 100644
--- a/tests/brokers/redis/test_test_client.py
+++ b/tests/brokers/redis/test_test_client.py
@@ -5,6 +5,7 @@
 from faststream import BaseMiddleware
 from faststream.exceptions import SetupError
 from faststream.redis import ListSub, RedisBroker, StreamSub, TestRedisBroker
+from faststream.redis.testing import FakeProducer
 from tests.brokers.base.testclient import BrokerTestclientTestcase
 
 
@@ -18,6 +19,9 @@ def get_broker(self, apply_types: bool = False) -> RedisBroker:
     def patch_broker(self, broker: RedisBroker) -> TestRedisBroker:
         return TestRedisBroker(broker)
 
+    def get_fake_producer_class(self) -> type:
+        return FakeProducer
+
     async def test_rpc_conflicts_reply(self, queue):
         async with TestRedisBroker(RedisBroker()) as br:
             with pytest.raises(SetupError):
@@ -226,3 +230,17 @@ async def test_publish_to_none(
         async with self.patch_broker(broker) as br:
             with pytest.raises(ValueError):  # noqa: PT011
                 await br.publish("hello")
+
+    @pytest.mark.redis()
+    async def test_broker_gets_patched_attrs_within_cm(self):
+        await super().test_broker_gets_patched_attrs_within_cm()
+
+    @pytest.mark.redis()
+    async def test_broker_with_real_doesnt_get_patched(self):
+        await super().test_broker_with_real_doesnt_get_patched()
+
+    @pytest.mark.redis()
+    async def test_broker_with_real_patches_subscribers_and_subscribers(
+        self, queue: str
+    ):
+        await super().test_broker_with_real_patches_subscribers_and_subscribers(queue)
diff --git a/tests/tools.py b/tests/tools.py
index 48df98a0a1..3ff9f57186 100644
--- a/tests/tools.py
+++ b/tests/tools.py
@@ -1,4 +1,5 @@
 import inspect
+from functools import wraps
 from typing import Any, Iterable
 from unittest.mock import MagicMock
 
@@ -8,11 +9,13 @@ def spy_decorator(method):
 
     if inspect.iscoroutinefunction(method):
 
+        @wraps(method)
         async def wrapper(*args, **kwargs):
             mock(*args, **kwargs)
             return await method(*args, **kwargs)
     else:
 
+        @wraps(method)
         def wrapper(*args, **kwargs):
             mock(*args, **kwargs)
             return method(*args, **kwargs)