'pytest can't find module in Lambda function
I have a Lambda, created using the SAM CLI, with a single function. The function itself works just fine (both locally and when deployed) however pytest fails, suggesting that my function is unable to import a module within my directory structure.
Note that the directory structure is shown at the bottom of the question.
# All commands are run from the 'Lambda' directory.
# With the 'sam' commands, the expected message is returned both locally and when deployed.
sam build -u
sam local invoke MyFunction --event events/405-get.json
sam deploy
# But the 'pytest' command fails.
python -m pytest tests/unit -v
The only tests I have at present are to ensure that an error is returned when the HTTP method is not GET, DELETE or PUT. The line from core.exception import MethodNotAllowedException is indeed at line 2 of 'my-function/app.py', and as mentioned previously, the function is able to import this module without issue when running locally or when deployed to AWS.
Here is the error that pytest is encountering.
my-function/app.py:2: in <module>
from core.exception import MethodNotAllowedException
E ModuleNotFoundError: No module named 'core'
I have tried to move the modules to the same directory as 'app.py' but import still fails.
my-function/app.py:2: in <module>
from exception import MethodNotAllowedException
E ModuleNotFoundError: No module named 'exception'
I did also try to use relative imports, so I changed line 2 of 'my-function/app.py' to from .core.exception import MethodNotAllowedException. However, while pytest is now able to import the module, the function itself fails when using sam local invoke. So relative imports I believe are a non-starter.
Unable to import module 'app': attempted relative import with no known parent package
How can I get pytest to work with my function?
test_handler.py
from proxy import app
import json
import os
import pytest
@pytest.fixture(scope='function')
def mock_context(mocker):
mock_context = mocker.MagicMock()
mock_context.aws_request_id = '00000000-0000-1000-0000-000000000000'
return mock_context
@pytest.fixture(scope='function')
def event(request):
dir = '{0}/../../events'.format(os.path.dirname(os.path.abspath(__file__)))
name = request.param
path = '{0}/{1}'.format(dir, name)
with open(path) as f:
event = f.read()
return event
class TestMethodNotAllowed:
@pytest.mark.parametrize(
'event', ['405-get.json'], indirect=True)
def test_405_get(self, event, mock_context):
'''Test a GET event.'''
response = app.lambda_handler(json.loads(event), mock_context)
body = json.loads(response['body'])
assert response['statusCode'] == 405
assert 'exception' in body
assert 'error_message' in body['exception']
assert body['exception']['error_message'] == 'Method not allowed.'
assert body['exception']['error_type'] == 'MethodNotAllowedException'
assert body['exception']['http_method'] == 'GET'
# Tests for PUT and DELETE also exist.
File Structure
Lambda
├── __init__.py
├── samconfig.toml
├── template.yaml
├── events
| ├── 405-delete.json
| ├── 405-get.json
| └── 405-put.json
├── my-function
| ├── init.py
| ├── app.py
| ├── requirements.txt
| └── core
| ├── __init__.py
| ├── exception.py
| ├── someotherfile.py
| └── morefiles.py
└── test
├── __init__.py
├── requirements.txt
└── unit
├── __init__.py
└── test_handler.py
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
