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.

aws lambda logo

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.

hol up

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.

API stages

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 🍾