Testing AWS Lambda and API Gateway
Recently, I had to fix some bugs in a Python AWS lambda which gets triggered by an API Gateway. I’ve found that the way that Lambdas work makes you want to develop it badly. So I hope you can use these tips when you develop your Lambdas to do it better.
I worked on a Python Lambda, so this is what you’ll see in this post.
How to test AWS Lambda + API Gateway successfully
The main thing I found to be useful during the development process of AWS Lambdas that are triggered by a Gateway was testing them automatically, in two ways:
Whitebox: Cover the logic in regular unit tests
This is good advice in general, but cover as much of the logic in UTs. To do that you’ll probably need to add a parameter to the Lambda that checks if it’s in testing mode, like so:
is_testing = (params['querystring'].get('testing', False) == "true")
res = handle_get(params, real_context, testing=is_testing)
And when it’s in testing mode, you need to avoid adding stuff to DBs. The correct way to do this is with a proper DAL and a mock that checks that the correct data was inserted: this is the classic dependency injection example.
Now you can write usual unit tests to tests all the parts of your lambda completely decoupled from the fact it’s deployed on the cloud.
Blackbox: Cover the Gateway API
Now comes the fun part. We’ll test the Gateway API using python’s requests
module and asserting the result.
Gateway API Stages
You SHOULD create two stages for your API: prod
(which you probably already have) and dev
. Here’s a link to the documentation but just look at the image below and you’ll get the gist. MAKE SURE YOUR UNIT TESTS ARE TESTING THE dev
INVOKE URL.
Writing the tests
Here’s the template. Copy, paste, and change according to your case. Some interesting stuff in this example:
- Faking user agent
- Asserting on status codes and content
- Logging makes it EASY to LEARN what the lambda does - run the tests and you get all the data you need.
import pytest
import logging
import requests
import json
from fake_useragent import UserAgent
ua = UserAgent()
logger = logging.getLogger(__name__)
# Note the /dev and the ?testing=true
API_GATEWAY_URL = "https://XXXXXXXXXX.execute-api.RE-GION-1.amazonaws.com/dev?testing=true"
def test_api_gateway_testcase_name_here():
url_to_test = API_GATEWAY_URL + "&some=parameters&for=this_test_case"
logger.info(f"url: {url_to_test}")
response = requests.get(url_to_test, headers={"User-Agent": ua.chrome})
logger.info(
f"the response error code is {response.status_code}\n"
f"the json of the response is {json.dumps(response.json(), indent=2)}")
assert response.status_code == 200
assert "expected_key" in response.json()
assert response.json()["expected_key"] == "expected_value"
def test_api_gateway_redirect_example():
url_to_test = API_GATEWAY_URL + "&some=parameters&for=this_test_case"
logger.info(f"url: {url_to_test}")
response = requests.get(url_to_test, headers={"User-Agent": ua.chrome}, allow_redirects=False) # <---- Note this
logger.info(
f"the response error code is {response.status_code}\n"
f"the json of the response is {json.dumps(response.json(), indent=2)}")
assert response.status_code == 302
redirect_location = response.headers["location"]
logger.info(f"Redirecting to...{redirect_location}")
assert "something_about_the_location" in redirect_location
Choose which tests you want to run using pytest
’s -k
switch
Now you can choose which tests are running with the -k
switch:
# This one runs all the tests
python -m pytest -v
# This one runs the blackbox tests only (slow, goes out to the internet)
python -m pytest -v -k "api_gateway"
# This one runs the whitebox tests only
python -m pytest -v -k "not api_gateway"
What now
Remember to deploy any changes in the code to prod 🍾