Reputation: 17555
I am writing a unit test for the following function, and I'm looking first at the case where we reach the last line.
from azureml.core.authentication import InteractiveLoginAuthentication, ServicePrincipalAuthentication
def get_authentication() -> Union[InteractiveLoginAuthentication, ServicePrincipalAuthentication]:
service_principal_id = get_secret_from_environment(SERVICE_PRINCIPAL_ID, allow_missing=True)
tenant_id = get_secret_from_environment(TENANT_ID, allow_missing=True)
service_principal_password = get_secret_from_environment(SERVICE_PRINCIPAL_PASSWORD, allow_missing=True)
if service_principal_id and tenant_id and service_principal_password:
return ServicePrincipalAuthentication(
tenant_id=tenant_id,
service_principal_id=service_principal_id,
service_principal_password=service_principal_password)
logging.info("Using interactive login to Azure. To use Service Principal authentication")
return InteractiveLoginAuthentication()
And here is my very simple unit test:
@patch("azureml.core.authentication.InteractiveLoginAuthentication")
def test_get_authentication(mocked_interactive_authentication: Any) -> None:
util.get_authentication()
assert mocked_interactive_authentication.called
We never reach the assert
line of my unit test because InteractiveLoginAuthentication()
raises the exception
TypeError: super() argument 1 must be type, not MagicMock
Why is get_authentication
calling the actual constructor for InteractiveLoginAuthentication
and not using my patch?
Upvotes: 1
Views: 1196
Reputation: 10719
It is not using your patch because the one that you patched is the root source class, which isn't the version already imported by your util at the time the test run was started. This means that the imported InteractiveLoginAuthentication
within util remains the same and not the patched version.
Update:
My answer has been a bit late :D On top of the answer from @dumbledad which is to patch the imported InteractiveLoginAuthentication
of the util instead of the root source class, another approach is by reloading the util. After the patch has been made to the root source class, reloading the util module would then reflect the patch made.
azureml/core/authentication.py
class InteractiveLoginAuthentication:
def __init__(self):
print("Actual InteractiveLoginAuthentication constructed!")
util.py
from azureml.core.authentication import InteractiveLoginAuthentication
def get_authentication() -> InteractiveLoginAuthentication:
print("Using interactive login to Azure. To use Service Principal authentication")
return InteractiveLoginAuthentication()
test_util.py
import sys
from typing import Any
from unittest.mock import patch
import util
@patch("azureml.core.authentication.InteractiveLoginAuthentication")
def test_get_authentication_2(mocked_interactive_authentication: Any) -> None:
from importlib import reload
reload(sys.modules['util'])
util.get_authentication()
assert mocked_interactive_authentication.called
Output:
$ pytest -rP
===================================================================================== test session starts ======================================================================================
collected 1 item
test_util.py . [100%]
============================================================================================ PASSES ============================================================================================
__________________________________________________________________________________ test_get_authentication_2 ___________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call -------------------------------------------------------------------------------------
Using interactive login to Azure. To use Service Principal authentication
====================================================================================== 1 passed in 0.02s =======================================================================================
Upvotes: 0
Reputation: 17555
It is because I was not specifying the patch correctly. Instead of using the import
line as the template for my patch, I should have used the module path of the function being tested. In my case that means replacing this:
@patch("azureml.core.authentication.InteractiveLoginAuthentication")
def test_get_authentication(mocked_interactive_authentication: Any) -> None:
util.get_authentication()
assert mocked_interactive_authentication.called
with this:
@patch("health.azure.azure_util.InteractiveLoginAuthentication")
def test_get_authentication(mocked_interactive_authentication: Any) -> None:
util.get_authentication()
assert mocked_interactive_authentication.called
(I don't know how often I am going to make this mistake before it finally sinks in!)
Upvotes: 2