1

I have a very simple Google Cloud Function written in Python and it makes a reference to Google's Secret manager via their Python library.

The code is very simple and it looks like this:

import os
from google.cloud import secretmanager
import logging

client = secretmanager.SecretManagerServiceClient()
secret_name = "my-secret"
project_id = os.environ.get('GCP_PROJECT')
resource_name = "projects/{}/secrets/{}/versions/latest".format(project_id, secret_name)
response = client.access_secret_version(resource_name)
secret_string = response.payload.data.decode('UTF-8')

def new_measures_handler(data, context):
    logging.info(secret_string)
    print('File: {}.'.format(data['name']))

and then I have my simple unit test which is trying to take advantage of monkey patching:

import main

def test_print(capsys, monkeypatch):
    # arrange
    monkeypatch.setenv("GCP_PROJECT", "TestingUser")
    monkeypatch.setattr(secretmanager, "SecretManagerServiceClient", lambda: 1)

    name = 'test'
    data = {'name': name}

    # act
    main.new_measures_handler(data, None)
    out, err = capsys.readouterr()

    #assert
    assert out == 'File: {}.\n'.format(name)

Everything goes well with the mock for the environment variable but I can not mock secretmanager. It keeps on trying to call the actual API. My ultimate goal is to mock secretmanager.SecretManagerServiceClient() and make it return an object which later on can be used by: client.access_secret_version(resource_name) (which I will need to mock as well, I think)

martineau
  • 119,623
  • 25
  • 170
  • 301
user2128702
  • 2,059
  • 2
  • 29
  • 74
  • Try: `monkeypatch.setattr(main.secretmanager, "SecretManagerServiceClient", lambda: 1)`. It has to be done this way because you import `secretmanager` into the namespace of `main`. – jordanm Mar 03 '20 at 00:06
  • @jordanm I tried that but it keeps on failing with the same error. Tries to call the actual API. – user2128702 Mar 03 '20 at 00:09
  • 3
    Ah, I see the issue. It's because you are calling `SecretManagerServiceClient()` in the top level scope. This means the object is created when `import main` is executed, which is before your monkeypatch. Use `if __name__ == '__main__'` – jordanm Mar 03 '20 at 00:12
  • @jordanm So, your suggestion is to have `if __name__ == '__main__'` above the function declaration and put the object initializations (`client = secretmanager.SecretManagerServiceClient()` etc.) inside of it? – user2128702 Mar 06 '20 at 16:45
  • Yes, everything that's above the function definition and below the imports should go in that conditional block. – jordanm Mar 06 '20 at 16:48
  • I am quite new to Python and I was wondering if it is a good practice to keep the function definition itself also inside the conditional block or not? Btw, you can add an answer and I will accept it. – user2128702 Mar 06 '20 at 16:50
  • And I have noticed that if I leave it like that (only the the part between function definition and imports to be inside the conditional statement) in my test I am getting `NameError: global name 'client' is not defined` – user2128702 Mar 06 '20 at 17:04

1 Answers1

0

See my answer to this question for a working example of using unittest patching and mocking to mock Google API calls and return mock results: How to Mock a Google API Library with Python 3.7 for Unit Testing

pink spikyhairman
  • 2,391
  • 1
  • 16
  • 13