0

Currently having difficulty running tests on a python file on my local machine meant to be deployed to AWS(I want to run the tests only on my local machine).

The tests were implemented using unittest, boto3, moto and pytest.

2 files are contained in the same directory called

Main file being tested (lambda_function.py) Test File (test_api_login.py)

See the errors output EDITED

/usr/bin/python3 "/Applications/PyCharm CE.app/Contents/plugins/python-ce/helpers/pycharm/_jb_pytest_runner.py" --target test_api_login.py::test_login_to_api
Testing started at 12:24 PM ...
Launching pytest with arguments test_api_login.py::test_login_to_api --no-header --no-summary -q in /Users/myk/Documents/DUMP/apiLogin

============================= test session starts ==============================
collecting ... collected 1 item

test_api_login.py::test_login_to_api::test_my_test FAILED    [100%]
test_api_login.py:16 (test_login_to_api.test_my_test)
moto/core/models.py:118: in wrapper
    result = func(*args, **kwargs)
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/unittest/mock.py:1322: in patched
    with self.decoration_helper(patched,
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/contextlib.py:113: in __enter__
    return next(self.gen)
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/unittest/mock.py:1304: in decoration_helper
    arg = exit_stack.enter_context(patching)
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/contextlib.py:425: in enter_context
    result = _cm_type.__enter__(cm)
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/unittest/mock.py:1377: in __enter__
    self.target = self.getter()
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/unittest/mock.py:1552: in <lambda>
    getter = lambda: _importer(target)
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/unittest/mock.py:1224: in _importer
    thing = __import__(import_path)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    import requests
    import json
    
    import boto3
    
    ssm = boto3.client('ssm', region_name="us-east-1")
    base_url_obj = ssm.get_parameters(Names=["/prof-svc-int/dev/api-base-url"])
>   BASE_URL = base_url_obj['Parameters'][0]['Value']
E   IndexError: list index out of range

lambda_function.py:8: IndexError




============================== 1 failed in 3.28s ===============================

Process finished with exit code 1

See the code for the main file being tested (lambda_function.py)

import requests
import json

import boto3

ssm = boto3.client('ssm', region_name="us-east-1")
base_url_obj = ssm.get_parameters(Names=["/prof-svc-int/dev/api-base-url"])
BASE_URL = base_url_obj['Parameters'][0]['Value']

username_obj = ssm.get_parameters(Names=["/prof-svc-int/dev/api-username"])
USERNAME = username_obj['Parameters'][0]['Value']

password_obj = ssm.get_parameters(Names=["/prof-svc-int/dev/api-password"])
PASSWORD = password_obj['Parameters'][0]['Value']


def lambda_handler(event, context):
    username = USERNAME
    password = PASSWORD

    message = ''
    code = 400

    headers = {"Accept": "application/json", "Content-Type": "application/json"}

    try:
        url = f"{BASE_URL}v0/login"

        data = {"username": username, "password": password}

        r = requests.post(url, headers=headers, data=json.dumps(data))
        message = r.json()

    except Exception as e:
        message += f'An error occurred  {str(e)}'

    if "token" in message:
        token = message['token']
        print("token >> " + token)
        put_response = ssm.put_parameter(
            Name='/prof-svc-int/dev/api-token',
            Value=token,
            Type='String',
            Overwrite=True
        )
        print("put_response >> ", put_response)
        code = 200
    output = OutputObj(code,
                       message)

    json_str = json.dumps(output.__dict__)

    result = json.loads(json_str)

    return result


class OutputObj:
    def __init__(self, statusCode, body):
        self.statusCode = statusCode
        self.body = body

See the code for test File (test_api_login.py) EDITED

import unittest
from unittest.mock import patch
from moto import mock_ssm


@mock_ssm
@patch("lambda_function.lambda_handler.base_url_obj")
@patch("lambda_function.lambda_handler.BASE_URL")
@patch("lambda_function.lambda_handler.username_obj")
@patch("lambda_function.lambda_handler.USERNAME")
@patch("lambda_function.lambda_handler.password_obj")
@patch("lambda_function.lambda_handler.PASSWORD")
class test_login_to_api(unittest.TestCase):
    @mock_ssm
    def test_my_test(self):

        with patch('lambda_function.lambda_handler') as mock_post:
            mock_post.return_value.statusCode = 200

            out = mock_post
            code = out.code
            self.assertEqual(code, 200)

    if __name__ == "__main__":
        unittest.main()
MIike Eps
  • 421
  • 7
  • 24

1 Answers1

0

The call to ssm.get_parameters is executed the moment the file is imported.
But mock_ssm only starts afterwards, and is only active for the duration of the test.

One way to be absolutely sure that the boto3/SSM-methods are mocked correctly, is to import the lambda_function inside the mocked function:

@pytest.fixture(scope='function')
    def test_my_test(self, aws_credentials):

        import lambda_function

        with patch('lambda_function.lambda_handler') as mock_post:
            mock_post.return_value.statusCode = 200

            out = lambda_function.lambda_handler()

This will ensure that the call to ssm.get_parameters is executed while the mock is active.

Note that, as it stands, you'll still run into the problem that the SSM parameters do not exist. As part of your tests, you would have to create these parameters first (using boto3.client(..).put_parameter) before they can be read.

Bert Blommers
  • 1,788
  • 2
  • 13
  • 19
  • I updated the test File (test_api_login.py) according to your suggestions and got a different error. The issue I have is I am mocking the ssm parameters and want to also mock the API request but everything is still failing – MIike Eps Apr 25 '22 at 11:34
  • Note that, as it stands, you'll still run into the problem that the SSM parameters do not exist. As part of your test, or maybe as part of the setup, you would have to create these parameters first (using boto3.client(..).put_parameter) before they can be read. – Bert Blommers Apr 25 '22 at 20:40
  • Are you saying that I must connect to my AWS account using AWS CLI before the tests can work? Note that I am already mocking the AWS SSM service with moto – MIike Eps Apr 26 '22 at 12:38
  • No, I mean calling `put_parameter` within the mock, to ensure Moto knows which value to return. Think of Moto as an offline AWS replacement. At the start of the mock, Moto has a completely clean slate, a completely new account. If your code expects some data/resources to exist in AWS, you'll have to recreate the same environment in Moto – Bert Blommers Apr 26 '22 at 14:40
  • any suggestions on how to re create the same environment in moto so the test works – MIike Eps Apr 26 '22 at 15:51