0

I have the following problem:

My main.py contains:

from google.cloud import secretmanager    

secret_name = "abc"

def function_1(secret):
    client = secretmanager.SecretManagerServiceClient()
    name = f"projects/123/secrets/{secret}/versions/latest"
    response = client.access_secret_version(name=name)
    return response.payload.data.decode("UTF-8")

def function_2():
    secret = function_1(secret_name)
    return secret

secret = function_2()

and my test_main.py has:

def test_function_1():
    import main
    ...

When running this and other tests in test_main.py I get an error because by importing main function_2 is called and e.g. the access_secret_version method is called unmocked. I don't want to change my main.py and for example put secret = function_2() under if __name__=="__main__". I want to fix this in test_main.py.

I tried different things like

@patch('main.secretmanager.SecretManagerServiceClient')
@patch('main.secretmanager.SecretManagerServiceClient.access_secret_version')
def test_function_1():
    import main
    ...

but import main is always calling these methods unmocked. How can I do this? How can I mock what function_2 is calling?

Here Mock function called on import the OP found a similar workaround.

Thank you in advance for any help.

Michael W.
  • 107
  • 8
  • Putting that code unter under `if __name__ == "__main__"` is actually the correct way to do this and the accepted practice in Python - anything else would just be a workaround. Is there a reason why you don't want to use it? – MrBean Bremen Mar 13 '22 at 19:14
  • I have a flask app in `main.py` and I want `secret = function_2()` to be a global variable that is available when starting the app. I tried to find out how to call the function before/after `app.run` for that but could not successfully make it work so that `secret` is assigned before referenced in other functions so I thought fixing the problem in the unit test is easier. – Michael W. Mar 13 '22 at 19:42
  • You already have a solution, but generally you could have used a function instead of a global variable (that also could initialize a global variable lazily if needed). – MrBean Bremen Mar 13 '22 at 20:16

1 Answers1

1

The module in the @patch decorator should be google.cloud instead of main, otherwise you're importing from main in order to patch (which runs function_2 before it's patched).

@patch('google.cloud.secretmanager.SecretManagerServiceClient')
SuperStormer
  • 4,997
  • 5
  • 25
  • 35
  • That's not correct - it would not patch the needed object, see [where to patch](https://docs.python.org/3/library/unittest.mock.html#id6). – MrBean Bremen Mar 13 '22 at 19:19
  • @MrBeanBremen When you `import main` within the test function, doesn't it look up the patched function from `google.cloud.secretmanager` for the first time? – SuperStormer Mar 13 '22 at 19:23
  • It works on my test script: https://paste.pythondiscord.com/ceneqiduje – SuperStormer Mar 13 '22 at 19:26
  • It works with `request` as this is a module. It does indeed look up `google.cloud.secretmanager` at first load, and creates a local reference to `secretmanager`, which will not be mocked, if you mock `google.cloud.secretmanager` instead of `main.secretmanager`. – MrBean Bremen Mar 13 '22 at 19:33
  • "It works with request as this is a module." ... and `google.cloud.secretmanager` is also a module. – SuperStormer Mar 13 '22 at 19:48
  • Just tested with the actual code, works for me. https://paste.pythondiscord.com/gehovozuzi – SuperStormer Mar 13 '22 at 19:49
  • @SuperStormer Thank you very much it worked. I didn't think about it but I'm also calling `requests.post` in function_2 which I'm importing with `import requests`. How does the patch look like here? I tried `@patch('requests')` but no success. – Michael W. Mar 13 '22 at 19:58
  • 2
    I take it all back - I wasn't aware that `secretmanager` is a separate module - in this case it will work. As for `requests` - you should probably patch `requests.post`. – MrBean Bremen Mar 13 '22 at 20:05