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?
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.
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.
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.
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.
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.
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.
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.
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:
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)]
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With