Unit Testing AWS Lambda with Python and Mock AWS Services Using Moto ๐
Content
Quality Assurance and Resilience of AWS Lambda with Moto ๐โจ
๐ Introduction
When developing serverless event-driven applications with AWS Lambda, ensuring the reliability and functionality of individual components is important.
Unit testing serves as a fundamental practice to swiftly identify and isolate potential issues within AWS Lambda function code.
This blog post explores the application of unit testing techniques for Python-based AWS Lambda functions, particularly focusing on interactions with various AWS services. In this context, the Moto library emerges as a valuable tool for simulating AWS resources, enabling the testing of Python code that interfaces with services such as DynamoDB and S3.
Through the integration of Moto with pytest, this post aims to provide insights into effectively testing Python-based AWS Lambda functions.
๐ ๏ธ What is Moto and Why Use It?
When testing code that interacts with cloud services, it's essential to mimic the functionality of those services. However, replicating them completely locally can be challenging. This is where Moto, a library that allows mocking AWS services, comes into play.
Moto is particularly useful when testing Python code that interacts with AWS services like DynamoDB, S3, and others. It enables developers to simulate AWS resources, making it easier to test code that interfaces with various AWS services.
By using Moto in combination with pytest, developers can ensure comprehensive testing of their Python code, thereby enhancing the reliability and quality of their applications.
Moto has gained recognition for its ability to simplify testing in the cloud, making it a valuable tool for developers working on serverless applications. By using Moto, developers can test their code without the need to directly access real AWS resources, ultimately streamlining the testing process and contributing to the overall robustness of their applications.
๐งฉ Decorator Approach
The decorator approach simplifies AWS service mocking using Moto in the eu-west-1
region. The @mock_ec2
decorator encapsulates the test function, ensuring automatic management of the Moto mock environment for seamless testing.
import boto3
from moto import mock_ec2
from main import create_instances
@mock_ec2
def test_create_instances_with_decorator(self):
instance_count = 1
image_id = 'ami-02ed82f3a38303e6f'
create_instances('eu-west-1', image_id, instance_count)
client = boto3.client('ec2', region_name='eu-west-1')
instances = client.describe_instances()['Reservations'][0]['Instances']
assert len(instances) == instance_count
assert instances[0]['ImageId'] == image_id
๐งฉ Context Manager Approach
In the Ireland region, the context manager-based approach facilitates AWS service mocking with Moto. Utilizing with mock_ec2():
, the test logic within the block benefits from the active Moto mock environment, maintaining clarity and proper resource management.
import boto3
from main import create_instances
from moto import mock_ec2
def test_create_instances_with_context_manager(self):
with mock_ec2():
instance_count = 1
image_id = 'ami-02ed82f3a38303e6f'
create_instances('eu-west-1', image_id, instance_count)
client = boto3.client('ec2', region_name='eu-west-1')
instances = client.describe_instances()['Reservations'][0]['Instances']
assert len(instances) == instance_count
assert instances[0]['ImageId'] == image_id
๐งฉ Raw Approach
For explicit control over the Moto mock environment, the raw approach is demonstrated in the Ireland region. Manually starting and stopping the mock using mock.start()
and mock.stop()
provides flexibility, as shown in the following example.
import boto3
from main import create_instances
from moto import mock_ec2
def test_create_instances_raw(self):
mock = mock_ec2()
mock.start()
instance_count = 1
image_id = 'ami-02ed82f3a38303e6f'
create_instances('eu-west-1', image_id, instance_count)
client = boto3.client('ec2', region_name='eu-west-1')
instances = client.describe_instances()['Reservations'][0]['Instances']
assert len(instances) == instance_count
assert instances[0]['ImageId'] == image_id
mock.stop()
๐ฉ Local Lambda Testing
Moto uses decorators to intercept and simulate responses to and from AWS resources. By adding a decorator for a given AWS service, subsequent calls from the module to that service will be re-directed to the mock.
In this section, we'll couple the Moto library and pytest framework to have clean and scalable unit tests. Let's test a serverless app using Pytest and Moto. Our goal? Check if a piece of code (AWS Lambda) works right. We'll focus on the Lambda function, not AWS services. We'll use Pytest for testing and Moto to pretend AWS services. This keeps tests clear and reliable.
๐ Lambda Function Code
This file (main.py
) contains the main Lambda function (lambda_handler
) that manipulates data and uploads it to an S3 bucket. It utilizes the Boto3 library to interact with AWS services.
import boto3
def lambda_handler(event, context):
# Sample Lambda function that manipulates data and uploads to S3
data_to_upload = "Hello, Moto!"
# AWS S3 Configuration
s3_bucket = "your-s3-bucket"
s3_key = "example.txt"
# Upload to S3
s3 = boto3.client("s3")
s3.put_object(Body=data_to_upload, Bucket=s3_bucket, Key=s3_key)
return {"statusCode": 200, "body": "Data uploaded to S3 successfully."}
๐ ๏ธ Pytest Configuration
conftest.py
serves as a configuration file for Pytest. In this example, it defines two fixtures:
- aws_credentials: A fixture to set up mocked AWS credentials using environment variables.
- s3_mock: A fixture that uses Moto's
mock_s3
context manager to mock the S3 service. It depends on theaws_credentials
fixture to ensure mocked credentials are available.
import pytest
import os
from moto import mock_s3
@pytest.fixture(scope='function')
def aws_credentials():
# Mocked AWS Credentials for Moto
os.environ['AWS_ACCESS_KEY_ID'] = 'fake-access-key'
os.environ['AWS_SECRET_ACCESS_KEY'] = 'fake-secret-key'
os.environ['AWS_SECURITY_TOKEN'] = 'fake-security-token'
os.environ['AWS_SESSION_TOKEN'] = 'fake-session-token'
@pytest.fixture(scope='function')
def s3_mock(aws_credentials):
with mock_s3():
yield boto3.client('s3')
๐งช Unit Test
test_main.py
is a Pytest test file that contains a single unit test (test_lambda_handler
). This test checks if the Lambda function in main.py
successfully uploads a file to an S3 bucket. It uses the s3_mock
and aws_credentials
fixtures from conftest.py
to set up the required mocking environment.
import pytest
import boto3
from main import lambda_handler
def test_lambda_handler(s3_mock, aws_credentials):
# Event and Context are not used in this example
event = {}
context = {}
# Invoke the Lambda function
response = lambda_handler(event, context)
# Check if the S3 object was created successfully
assert response["statusCode"] == 200
# Check if the object exists in S3
objects = s3_mock.list_objects(Bucket='your-s3-bucket')
assert objects["Contents"][0]["Key"] == "example.txt"
๐ Running the Unit Tests
The unit testing framework can be run using the Python pytest
utility. To ensure network isolation and verify the unit tests are not accidentally connecting to AWS resources, the pytest-socket project provides the ability to disable network communication during a test.
pytest -v --disable-socket -s test_main.py
The pytest
command results in a PASSED or FAILED status for each test. A PASSED status verifies that your unit tests, as written, did not encounter errors or issues.
๐ Conclusion
In this guide, we explored some ways for mocking AWS responses with Moto, offering a swift and cost-free approach to testing Python code. When paired with Pytest, it delivers scalable and tidy unit tests specifically tailored for AWS interactions.
๐ What Next?
You can keep an eye on this repository as I continue to develop it and add more use cases and integrations!
Until next time, ใคใฅใ ๐
๐ก Thank you for Reading !! ๐๐ป๐๐, see you in the next blog.๐ค Until next time ๐
๐ Thank you for sticking up till the end. If you have any questions/feedback regarding this blog feel free to connect with me:
โป๏ธ LinkedIn: https://www.linkedin.com/in/rajhi-saif/
โป๏ธ X/Twitter: https://x.com/rajhisaifeddine
The end โ๐ป
๐ฐ Keep Learning !! Keep Sharing !! ๐ฐ
๐ Stay updated
Subscribe to our newsletter for more insights on AWS cloud computing and containers.