Testing AWS Lambda Function in Python

In this Blog Article, I show some techniques to test python-based AWS Lambda Functions.

Python is a perfect match for the AWS ecosystem. The reasons are boto3 and moto - there is nothing like this in other languages.

The project is a small poetry project, which gets deployed by AWS CDK, but these are irrelevant details for this post. The code creates signed cookies to request resources from a protected CloudFront Distribution.

More details on the implementation can be found in the articles:

Tools to test the code are:

  • pytest - the testing framework
  • moto - to mock aws-services
  • python-lambda-local - to run the lambda locally and perform an exploratory test against a real endpoint

Topics included:

  • test and fixture setup with conftest.py
  • how to create fake_time in python
  • how to mock aws-services in general with the secretsmanager as an example
  • how to expect an raised error

#1 test and fixture setup with conftest.py

The file tests/conftest.py should contain all fixtures which are used by the tests.

To mock AWS services, it is required to set some ENV-Vars:

def aws_credentials():
    os.environ["AWS_ACCESS_KEY_ID"] = "testing"
    os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
    os.environ["AWS_SECURITY_TOKEN"] = "testing"
    os.environ["AWS_SESSION_TOKEN"] = "testing"
    os.environ["AWS_REGION"] = "eu-central-1"

This fixture can be included in your test later like this:

def test_aws_service(aws_credentials):
    # ... test code ..

But, you can not only include fixtures in your test - fixtures can also be included in other fixtures, too:

def mock_aws_service(aws_credentials):
    # ... setup aws service mock ..

#2 Create a fake_time in python

There are many different approaches to implementing this, according to this StackOverflow answer.

The technique explained by Shinji Matsumoto in the answer worked best for me.

def fake_time(monkeypatch):
    datetime_mock = MagicMock(wraps=datetime.datetime)
    datetime_mock.now.return_value = datetime.datetime(2015, 9, 21)
    monkeypatch.setattr(datetime, "datetime", datetime_mock)

    yield datetime_mock.now()

Two things happening here:

  1. Wrap datetime.datetime in MagickMock

    By using MagickMock, the return_value of this function can be overwritten.

  2. Monkeypatch datetime module

    Monkeypatch is a pytest fixture to modify values and it can be used to replace the datetime.datetime implementation by datetime_mock.

At the end, the timestamp of the mocked datetime is yielded so the test can use the timestamp as input for the system under test.

#3 Mock AWS Services, with SecretsManager as an example

To mock AWS Services, we will use the excellent moto lib.

In this Example I will mock the secretsmanager create_secret method, so the system under test can call get_secret_value to get the response from the mocked service.

There are two convenient ways to mock the service with moto:

  • using the mock_* function as decorator
  • using the mock_* function as context manager

I will use the mock_* function as context manager:

def mock_cdn_secret(aws_credentials, env_cdn_key):
    with moto.mock_secretsmanager():
        secretsmanager = boto3.client("secretsmanager", region_name="eu-central-1")
                    "private-key": """
                    "public-key": """
-----END PUBLIC KEY-----


As you see, instead of mocking the response with a fixture, we mock the AWS service simply by using it.

One important detail: The yield on the last line is important, don’t overlook it. If you do, the mock stops before the test starts.

#4 Expect an raised Error

This is a small one. It is important to test for expected Errors.

Following test expects that a PermissionError is raised:

def test_permission_check():
    with pytest.raises(PermissionError):
                "ownerId": "d005b2de-d70c-478a-8cc2-287fcb6d3b09",
                "source": {
                    "ownerId": "a43d3b16-e77f-4014-8d47-eb3d51004c84", 
                    "state": "DOC_STATE_READY"

Pretty straight forward!

#5 Exploratory testing by running the lambda locally

To run lambdas locally, use the tool python-lambda-local.

Have the event and optionally the env-vars in a json-file, then run the lambda like:

python-lambda-local -e tests/fixture_env.json index.py tests/fixture_query.json

If needed add your AWS_PROFILE so the lambda can access AWS resources or start local-stack for full local testing.

2022-07-20 20:40