All we need is an easy explanation of the problem, so here it is.
I am trying to patch/mock a method (
AsyncHTTPClient.fetch) that is being called in two different places in my program: Firstly in
tornado_openapi3.testing and secondly in
my_file. The problem is that the method is being patched in the first location, which breaks the functionality of my tests.
import tornado class Handler(tornado.web.RequestHandler, ABC): def initialize(self): <some_code> async def get(self, id): <some_code> client = AsyncHTTPClient() response = await client.fetch(<some_path>) <some_code>
from tornado_openapi3.testing import AsyncOpenAPITestCase class HandlerTestCase(AsyncOpenAPITestCase): def get_app(self) -> Application: return <some_app> def test_my_handler(self): with patch.object(my_file.AsyncHTTPClient, 'fetch') as mock_fetch: f = asyncio.Future() f.set_result('some_result_for_testing') mock_fetch.return_value = f self.fetch(<some_path>)
From what I understood from various mocking tutorials (e.g. https://docs.python.org/3/library/unittest.mock.html),
fetch should only be patched/mocked in
my_file. How can I make sure that is the case?
How to solve :
I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.
Cause of the issue
The imported class
my_file.py is actually just a reference to tornado’s original
from x import y statements are variable assignments in that a new variable called
y is created in the current file referencing the original object
And, since, classes are mutable objects, when you patch the
fetch method in the imported class, you are actually patching the
fetch method on the original class.
Here’s an example using variable assignment to illustrate this issue:
class A: x = 1 b = A # create a variable 'b' referencing the class 'A' b.x = 2 # change the value of 'x' attribute' of 'b' print(A.x) # Outputs -> 2 (not 1 because classes are mutable)
Like I said earlier,
from ... import ... statements are basically variable assignments. So the above illustration is what’s really happening when you patch the
Instead of patching a single method, patch the whole class:
with patch.object(my_file, 'AsyncHTTPClient') as mock_client: f = asyncio.Future() f.set_result('some_result_for_testing') mock_client.fetch.return_value = f self.fetch(<some_path>)
What’s happening this time is Python is reassigning the value of the local variable
AsyncHTTPClient to a mock object. There’s no mutation going on this time and, so, the original class is not affected.
Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂