API testing: How do I mock/patch a method in one place only?

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.

my_file.py:

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>

test_handler.py:

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.

Method 1

Cause of the issue

The imported class AsyncHTTPClient in my_file.py is actually just a reference to tornado’s original AsyncHTTPClient class.

Basically, 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 x.y.

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 fetch method.


Solution

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 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply