Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python function not using the mocked object

Coming from a PHP background I have encountered the following issue in writing Python unit tests:

I have function foo that uses a Client object in order to get a response from some other API:

from xxxx import Client
def foo (some_id, token):
    path = 'some/api/path'
    with Client.get_client_auth(token) as client:
        response = client.get(path,params).json()
        results = list(response.keys())
    .............

For this I have created the following unit test in another python file.

from yyyy import foo
class SomethingTestCase(param1, param2):
    def test_foo(self):
        response = [1,2,3]
        with patch('xxxx.Client') as MockClient:
            instance = MockClient.return_value
            instance.get.return_value = response
        result = foo(1,self.token)
        self.assertEqual(response,result)

I don't understand why foo isn't using the mocked [1,2,3] list and instead tries to connect to the actual API path in order to pull the real data.

What am I missing?

like image 777
Susy11 Avatar asked Sep 28 '17 08:09

Susy11


People also ask

What is Side_effect in mock Python?

side_effect: A function to be called whenever the Mock is called. See the side_effect attribute. Useful for raising exceptions or dynamically changing return values. The function is called with the same arguments as the mock, and unless it returns DEFAULT , the return value of this function is used as the return value.

What are mock objects in Python?

A mock object substitutes and imitates a real object within a testing environment. It is a versatile and powerful tool for improving the quality of your tests. One reason to use Python mock objects is to control your code's behavior during testing.

What is the difference between mock and MagicMock?

So what is the difference between them? MagicMock is a subclass of Mock . It contains all magic methods pre-created and ready to use (e.g. __str__ , __len__ , etc.). Therefore, you should use MagicMock when you need magic methods, and Mock if you don't need them.

What is Pytest mock in Python?

In pytest , mocking can replace the return value of a function within a function. This is useful for testing the desired function and replacing the return value of a nested function within that desired function we are testing.

What is a Python mock object?

A Python mock object contains data about its usage that you can inspect such as: Understanding what a mock object does is the first step to learning how to use one. Now, you’ll see how to use Python mock objects. The Python mock object library is unittest.mock. It provides an easy way to introduce mocks into your tests.

What happens if you misspell a Python mock object’s attribute?

As mentioned before, if you change a class or function definition or you misspell a Python mock object’s attribute, you can cause problems with your tests. These problems occur because Mock creates attributes and methods when you access them.

What happens when you call a mocked function in Python?

A .side_effect defines what happens when you call the mocked function. To test how this works, add a new function to my_calendar.py: get_holidays () makes a request to the localhost server for a set of holidays. If the server responds successfully, get_holidays () will return a dictionary. Otherwise, the method will return None.

How do I mock an API call in Python?

Mocking in Python is done by using patch to hijack an API function or object creation call. When patch intercepts a call, it returns a MagicMock object by default. By setting properties on the MagicMock object, you can mock the API call to return any value you want or raise an Exception. The overall procedure is as follows:


2 Answers

You are doing 3 things wrong:

  • You are patching the wrong location. You need to patch the yyyy.Client global, because that's how you imported that name.

  • The code-under-test is not calling Client(), it uses a different method, so the call path is different.

  • You are calling the code-under-test outside the patch lifetime. Call your code in the with block.

Let's cover this in detail:

When you use from xxxx import Client, you bind a new reference Client in the yyyy module globals to that object. You want to replace that reference, not xxxx.Client. After all, the code-under-test accesses Client as a global in it's own module. See the Where to patch section of the unittest.mock documentation.

You are not calling Client in the code. You are using a class method (.get_client_auth()) on it. You also then use the return value as a context manager, so what is assigned to client is the return value of the __enter__ method on the context manager:

with Client.get_client_auth(token) as client:

You need to mock that chain of methods:

with patch('yyyy.Client') as MockClient:
    context_manager = MockClient.get_client_auth.return_value
    mock_client = context_manager.__enter__.return_value
    mock_client.get.return_value = response
    result = foo(1,self.token)

You need to call the code under test within the with block, because only during that block will the code be patched. The with statement uses the patch(...) result as a context manager. When the block is entered, the patch is actually applied to the module, and when the block exits, the patch is removed again.

Last, but not least, when trying to debug such situations, you can print out the Mock.mock_calls attribute; this should tell you what was actually called on the object. No calls made? Then you didn't patch the right location yet, forgot to start the patch, or called the code-under-test when the patch was no longer in place.

However, if your patch did correctly apply, then MockClient.mock_calls will look something like:

[call.get_client_auth('token'),
 call.get_client_auth().__enter__(),
 call.get_client_auth().__enter__().get('some/api/path', {'param': 'value'}),
 call.get_client_auth().__exit__(None, None, None)]
like image 70
Martijn Pieters Avatar answered Nov 06 '22 11:11

Martijn Pieters


You need to patch the Client object in the file where it is going to be used, and not in its source file. By the time, your test code is ran, the Client object would already have been imported into the file where you are hitting the API.

# views.py
from xxxx import Client

# test_file.py
...
with patch('views.Client') as MockClient: # and not 'xxxx.Client'
...

Moreover, since you're patching a context manager you need to provide a stub.

like image 21
hspandher Avatar answered Nov 06 '22 10:11

hspandher