Home > database >  How to mock config variables in python3?
How to mock config variables in python3?

Time:09-29

This is a AWS lambda function

#service.py
from configs import SENDER, DESTINATIONS
from constants import LOG_FORMAT
import logging

def send_mail(body, SENDER, DESTINATIONS):
    ...
    ...

In config files it's retrieving data from AWS param store

# configs.py
from handlers.ssm_handler import load_parameters
from common import constants
import os
environment = os.environ.get(constants.ENVIRONMENT)

JSON_BUCKET = load_parameters(constants.OT_ARCHIVAL_PREFIX environment constants.MIGRATION_BUCKET)
SENDER = load_parameters(constants.OT_ARCHIVAL_PREFIX environment constants.MAIL_SENDER)
DESTINATIONS = load_parameters(constants.OT_ARCHIVAL_PREFIX environment constants.MAIL_DESTINATIONS)
...

So when i try to test it

# test_service.py
from unittest import TestCase, main, mock
from service import send_mail

class TestMailService(TestCase):
     def test_service(self):
       with mock.patch('service.SENDER', '[email protected]') as mocked_sender:
         with mock.patch('service.DESTINATIONS', '[email protected]') as mocked_sender:
           with mock.patch('service.logging.Logger.info') as mocked_logging:
              send_mail(...)
              mocked_logging.assert_called_with('mail sent Successfully')

This test case is passing when I export AWS Security credentials. But it'wont, if i don't pass the credentials. I guess it's because in the service.py file it's opening the entire config.py file. So it would require sec credentials to call AWS. As a solution I tried mocking SENDER and DESTINATIONS. But it throws me error(expecting security tokens)

I want the unittest to be security token independent. Suggest a solution

CodePudding user response:

It happens because when you import configs.py e.g. via from configs import SENDER, DESTINATION, it would automatically run those statements that call load_parameters which in turn calls AWS SSM even if there are no active mocks/patches yet.

Solution 1

Try refactoring configs.py in a way that the setting of the variables would only happen upon an explicit call (and not upon import). The simplest implementation would be something like:

configs.py

import os

from common import constants
from handlers.ssm_handler import load_parameters


def get_params():
    environment = os.environ.get(constants.ENVIRONMENT)
    return {
        "SENDER": load_parameters(constants.OT_ARCHIVAL_PREFIX environment constants.MAIL_SENDER),
        "DESTINATIONS": load_parameters(constants.OT_ARCHIVAL_PREFIX environment constants.MAIL_DESTINATIONS),
    }

This would need some refactoring as the call to get_params must be inserted at the start of the AWS Lambda function call. This way, the calling of load_parameters which in turn uses AWS SSM would not be executed automatically and we can prepare our mocks/patches before it is called.

Solution 2

Don't import any file that would in turn import configs.py while there is no active mock/patch yet. Patch load_parameters first so that it doesn't connect to the actual AWS SSM. You can patch it manually or you can use the decorator @mock_ssm from moto. Only then we can safely import the files.

from unittest import TestCase, main, mock

from moto import mock_ssm

# from service import send_mail  # REMOVE THIS IMPORT!


@mock_ssm  # Option 1. Requires <pip install moto>. You have to setup SSM first as usual.
def test_service(mocker):  # Requires <pip install pytest-mock>
    mocker.patch('handlers.ssm_handler.load_parameters')  # Option 2
    # with mock.patch('handlers.ssm_handler.load_parameters') as mock_ssm:  # Option 3. This is equivalent to Option 2.

    mocker.patch('service.SENDER', '[email protected]')
    mocker.patch('service.DESTINATIONS', '[email protected]')

    from service import send_mail  # Import it here after the patches have taken effect
    send_mail(...)

  • Related