Reputation: 3867
I am really new to Python and unit tests and I'm trying to write unit tests for a handler right now.
A simplified version of the handler looks something like this:
some_handler.py
def handleEvent(event):
user = event....
action = event...
response = getIsAuthorized(user, action)
return convertToHttpResponse(response)
getIsAuthorized(user, action):
...
authorizer = SomeAuthorizer()
return authorizer.isAuthorized(user, action)
SomeAuthorizer is a simple class:
some_authorizer.py
class SomeAuthorizer(object):
__init__(self):
# some instantiation of stuff needed for the 3P client
...
# creation of some 3P auth client
self.client = Some3PClient(...)
is_authorized(user, action):
return self.client.is_authorized(user, action)
The code itself is working as expected, but my tests are giving me hell. I got it to work in some circumstances with:
@patch.object(SomeAuthorizer, 'is_authorized')
def test_authorization_request_not_authorized(self, mock_is_authorized):
mock_is_authorized.return_value = False
response = handle_request(test_event("test_user", "testing"))
assert response == status_code(200, False)
But then when another dev ran the tests on their box, it failed and it seemed like the reason was because they didn't have some environment stuff set up locally that the constructor of the Authorizer needed (the errors they got locally were issues creating an instance of SomeAuthorizer when the tests were running). But my understanding was that the constructor shouldn't even be getting called because it's being mocked? Is this not the case?
Any tips on how to get around this? I've been beating my head against the wall trying to understand how the mocks are working and what combination of patch / mock would work but it's not going far.
Upvotes: 2
Views: 1604
Reputation: 10709
Here's a working solution you might want to use as reference. This would mock the whole SomeAuthorizer
class thus its __init__()
would not be executed nor any of its methods like is_authorized()
.
File tree
$ tree
.
├── some_authorizer.py
├── some_handler.py
└── test_auth.py
some_authorizer.py
class Some3PClient:
def is_authorized(self, user, action):
return True # Real implementation returns True
class SomeAuthorizer(object):
def __init__(self):
print("Real class __init__ called")
self.client = Some3PClient()
def is_authorized(self, user, action):
print("Real class is_authorized called")
return self.client.is_authorized(user, action)
some_handler.py
from some_authorizer import SomeAuthorizer # The module we want to mock. To emphasize, the version we need to mock is "some_handler.SomeAuthorizer" and not "some_authorizer.SomeAuthorizer".
def handleEvent(event):
user = "event...."
action = "event..."
response = getIsAuthorized(user, action)
return convertToHttpResponse(response)
def getIsAuthorized(user, action):
authorizer = SomeAuthorizer()
return authorizer.is_authorized(user, action)
def convertToHttpResponse(response):
return (200, response)
test_auth.py
from unittest.mock import patch
from some_handler import handleEvent
def test_real_class():
response = handleEvent("test_event")
assert response == (200, True)
print(response)
@patch("some_handler.SomeAuthorizer") # As commented in some_handler.py, patch the full path "some_handler.SomeAuthorizer" and not "SomeAuthorizer" nor "some_authorizer.SomeAuthorizer". If you want to patch the latter for all that uses it, you have to reload the modules that imports it.
def test_mock_class(mock_some_authorizer):
mock_some_authorizer.return_value.is_authorized.return_value = False # Mock implementation returns False
response = handleEvent("test_event")
assert response == (200, False)
print(response)
Output
$ pytest -q -rP
============================================================================================ PASSES ============================================================================================
_______________________________________________________________________________________ test_real_class ________________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call -------------------------------------------------------------------------------------
Real class __init__ called
Real class is_authorized called
(200, True)
_______________________________________________________________________________________ test_mock_class ________________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call -------------------------------------------------------------------------------------
(200, False)
2 passed in 0.05s
Upvotes: 2