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")
CodePudding user response:
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.