I am attempting to mock an object to perform some testing
test.py
#patch("api.configuration.client.get_configuration")
def test(mock_client_get_configuration):
mock_client_get_configuration.return_value = "123"
result = code_under_test()
assert result
Inside code_under_test() I make the concrete call to get_configuration().
code.py
from api.configuration.client import get_configuration
def code_under_test():
config = get_configuration("a", "b")
However, whenever I run my test, get_configuration is always the concrete version and not my mock.
If I add a print in my test, I can see that mock_client_get_configuration is a MagicMock.
If I add a print inside code_under_test, then get_configuration is always the actual <function.... and not my mock.
I feel somewhere I am going wrong in how I create my mock. Perhaps it is because my mock does not mimic the two parameters needed on the concrete call so the signature is incorrect?
I'm using Python 3.9 and pytest 7.0.1.
Related
I have this python lambda on handler.py
def isolate_endpoints(event=None, context=None):
endpoint_id = event['event']['endpoint_id']
client = get_edr_client()
response = client.isolate_endpoints(endpoint_id=endpoint_id)
return response # E.g {"reply":{"status":"success", "error_msg":null}}
I want to write a unit test for this lambda. However after reading on the unittests and having seen actual implementations of the tests I can comfortably say I have no idea what is being tested exactly. (I know the theory but having hard time understanding the implementation of mocks, magicmocks etc.)
The unittest that I have right now is on test_calls and looks like this:
#mock.patch('project_module.utils.get_edr_client')
def test_isolate_endpoints(get_edr_client: MagicMock):
client = MagicMock()
get_edr_client.return_value = client
mock_event = {"event": {"endpoint_id":"foo"}}
resp = isolate_endpoints(event=mock_event) # Is it right to call this lambda directly from here?
assert resp is not None
expected = [call()] ## what is this ?? # What is supposed to follow this?
assert client.isolate_endpoints.call_args_list == expected
definition & body of get_edr_client in utils.py:
from EDRAPI import EDRClient
def get_edr_client():
return EDRClient(api_key="API_KEY")
I'll try to explain each aspect of the test you have written
#mock.patch('project_module.utils.get_edr_client')
This injects dependencies into your test. You are essentially patching the name 'project_module.utils.get_edr_client' with an auto-created MagicMock object that's passed to you as the test argument get_edr_client. This is useful to bypass external dependencies in your test code. You can pass mock implementations of objects that are used in your code that's under test, but don't need to be tested in this test itself.
def test_isolate_endpoints(get_edr_client: MagicMock):
client = MagicMock()
get_edr_client.return_value = client
Setup: You are setting up the external mock you patched into the test. Making it return more mock objects so that the code under test works as expected.
mock_event = {"event": {"endpoint_id":"foo"}}
resp = isolate_endpoints(event=mock_event)
Invoke: Calling the code that you want to test, with some (possibly mock) argument. Since you want to test the function isolate_endpoints, that's what you should be calling. In other tests you could also try passing invalid arguments like isolate_endpoints(event=None) to see how your code behaves in those scenarios.
assert resp is not None
Verify: Check if the value returned from your function is what you expect it to be. If your function was doing 2+2 this is the part where you check if the result equals 4 or not.
expected = [call()]
assert client.isolate_endpoints.call_args_list == expected
Verify side-effects: Your function has side-effects apart from returning a value. So you should check if those are happening properly or not. You expect the function to call isolate_endpoint on the client mock you injected earlier once. So you're checking if that method was called once with no arguments. The call() is for matching one invocation of the method with no arguments. The [call()] means only one invocation with no arguments. Then you just check if that is indeed the case with the assertion.
A much cleaner way to do the same this is to do this instead:
client.isolate_endpoints.assert_called_once()
Which does the same thing.
Below I have added some dummy code which exactly represent what I am trying to do.
I am importing a function from a class with an alias, I am doing this since the class is running the aliased version itself.
Things I have tried:
http://bhfsteve.blogspot.com/2012/06/patching-tip-using-mocks-in-python-unit.html This does not give a specific solution to the problem.
patched a.dummy.class.function.alias as a.dummy.class.function this allows execution but will not run the alias_function_mocker() as in the class the function is called as alias() and not function()
I tried running as a.dummy.class.function.alias but this results in an attribute error as alias isn't actually an attribute of class(because function() is)
from a.dummy.class import function as alias
def alias_function_mocker():
return 0
#patch("a.dummy.class.function.alias", side_effect=alias_function_mocker):
def test_function(mocked_function):
function_calling_alias()
return 0
What I think the problem roots from is that the class file is calling function using alias and there doesn't seem to be a way to patch aliased function calls. The simple and most naive solution I can think of is just do not use an alias.
You need to mock the object where it is. If you imported the module/function to file.py, you need to mock the object of this file.
If you are using the test in the same file of the method, this could be the solution:
from os.path import isdir as is_it_a_dir
from unittest.mock import patch
def check(path):
return is_it_a_dir(path)
with patch('__main__.is_it_a_dir') as mock_isitadir:
mock_isitadir.return_value = True
assert check('/any/invalid/path') == True
If your test is in another file, than your approch should work.
Just to complement Mauro Baraldi's answer, this is how to do it using decorators.
#patch("__main__.alias")
I have some code that uses functions as parameters and I've added some logging that includes __qualname__, this caused my unit tests to fail since the Mock object I passed in raises an AttributeError for __qualname__.
mock_func = Mock()
A simple solution to this problem is to manually add the expected attribute to the mock:
mock_func.__qualname__ = "mock_function"
Or add it to the spec of the mock when I create it:
mock_func = Moc(["__qualname__"])
But these solutions are unsatisfying since I would need to change them whenever I use a different built-in attribute (e.g. __name__).
Is there a simple way to create a Mock which behaves like a function?
The closest I found was this bug report that was opened on the wrong repository, and this request which has no replies.
You can simply use any function as spec for the mock.
mock_func = Mock(spec=max)
mock_func.__qualname__
>>> <Mock name='mock.__qualname__' id='140172665218496'>
I'm struggling with django mock; I have even simplified an unit test but the test is still failing. I want to verify that a method is called (even with any parameter), but the "assert_called_once_with" always returns False.
Currently I'm trying:
#patch('utils.make_reset_password')
def test_shouldHaveCalledMakeResetToken(self, mocked):
user = User.get(...)
make_reset_password(user)
mocked.assert_called_once_with(user)
Even this simple example is failing with:
AssertionError: Expected 'make_reset_password' to be called once. Called 0 times
How this is possible? What am I doing wrong?
Thanks in advance
You have to use full path to utils, e.g. #patch('my_app.utils.make_reset_password') and then in the test call a function that calls make_reset_password.
#patch('my_app.utils.make_reset_password')
def test_shouldHaveCalledMakeResetToken(self, mock_make_reset_password):
user = User.get(...)
function_under_test(user)
mock_make_reset_password.assert_called_once_with(user)
EDIT
The other thing that comes to my mind is you are not mocking the correct function. If make_reset_password is imported from utils in another module then you need to change the path in the #patch decorator.
For example
# my_module.py
from my_app.utils import make_reset_password
def run_make_reset_password(user):
make_reset_password(user)
# tests.py
#patch('my_app.my_module.make_reset_password')
def test_shouldHaveCalledMakeResetToken(self, mock_make_reset_password):
user = User.get(...)
run_make_reset_password(user)
mock_make_reset_password.assert_called_once_with(user)
I'm currently having my first real experience writing tests by creating a unittest suite in python, and I've run into a problem with my mock framework.
Currently, I have a function that has a line like this:
def data_function():
*Some code*
with AccountDBConnection(account_id) as adb: # Pulling data
data = adb.get_query_results(query)
*Some more code*
I'm trying to test that the function get_query_results is being called correctly. In my tests, I'm attempting to test it as so:
#patch('package.module_that_defines_data_function.AccountDBConnection')
def test_data_function(self, mock_AccountDBConnection):
*Other assertions*
mock_AccountDBConnection.get_query_results.assert_called_once_with(ANY)
*more assertions*
Unfortunately, whenever I try to run this test, I get an AssertionError stating get_query_results is never called. It most certainly is called, and I can't get it to recognize this. I think it's because the method is being called on an instance of an object that is created within the function, but I don't know how to get the mock to recognize. Does anyone have any suggestions?
Thanks a bunch!