AWS Compute Blog

A Simple Serverless Test Harness using AWS Lambda

Tim Wagner Tim Wagner, AWS Lambda General Manager

You can easily test a Lambda function locally by using either a nodejs runner or JUnit for Java. But once your function is live in Lambda, how do you test it?

One option is to create an API for it using Amazon API Gateway and then employ one of the many HTTP-based test harnesses out there. While that certainly works, in this article we’ll look at using Lambda itself as a simple (and serverless!) test platform. We’ll look at testing two categories – other Lambda functions and HTTPS endpoints – but you can apply these techniques to testing anything you want.

Unit Testing

In the Microservices without the Servers blog post we looked briefly at testing the image processing service we built using Lambda itself. The idea was simple: We create a “unit” test that calls another Lambda function (the functionality being tested) and records its output in DynamoDB. We can record the actual response of calling the function under test or just summarize whether it succeeded or failed. Other information, such as performance data (running time, memory consumed, etc.) can of course be added.

AWS Lambda includes a unit and load test harness blueprint, making it easy to create a custom harness. By default, the blueprint runs the function under test and records the response in a predetermined DynamoDB table. You can easily customize how results are recorded (e.g., sending them to Amazon SQS, Amazon Kinesis, or Amazon S3 instead of DymamoDB) and what is recorded (success, actual results, performance or other environmental attributes of running the function, etc.)

Using this blueprint is easy: It reads a simple JSON format to tell it what to call:

{
  "operation": "unit",
  "function": <Name of the Lambda function under test>,
  "resultsTable": "unit-test-results",
  "testId": "MyTestRun",
  "event": {
    /* The event you want the function under test to process goes here. */
  }
}

One of the nice things about this approach is that you don’t have to worry about infrastructure at any point in the testing process. The economics of Lambda functions work to your advantage: after the test runs, you’re not left owning a bunch of resources, and you don’t have to spend any energy spinning up or shutting down infrastructure. When you’re done with the results in DynamoDB, you can simply delete the corresponding rows or replace them with a single, summary row. Until then, you have the full power of a managed NoSQL database to access and manipulate your test run results.

The Lambda blueprint itself is intentionally very simple. You can add an existing test library if you want more structure, or just instantiate the blueprint and modify it for each function under test.

HTTPS Endpoint Testing

Lambda also includes a blueprint for invocating HTTPS endpoints, such as those created by Amazon API Gateway. You can combine these two blueprints to create a unit tester for any HTTPS service, including APIs created with API Gateway. Here’s an example of the unit test JSON from our image service test:

{
  "operation": "unit",
  "function": "HTTPSInvoker",
  "resultsTable": "unit-test-results",
  "testId": "LinuxConDemo",
  "event": {
    "options": {
      "host": "fuexvelc41.execute-api.us-east-1.amazonaws.com",
      "path": "/prod/ImageProcessingService",
      "method": "POST"
    },
    "data": {
      "operation": "getSample"
    }
  }
}

Load Testing

Lambda’s test harness blueprint can run in either of two modes: Unit and Load. Load testing enables you to use Lambda’s scalability to your advantage, running multiple unit tests asynchronously. As before, each unit test records its result in DynamoDB, so you can easily do some simple scale testing, all without standing up infrastructure. Simple queries in DynamoDB enable you to validate how many tests completed (and completed successfully). To do more elaborate post-hoc analysis, you can also hook up a Lambda function to DynamoDB as an event handler, allowing you to count results, perform additional validation, etc.

Here’s an example recreated from the image processing blog article that uses the unit and load test harness again, this time in “load test” mode:

{
  "operation": "load",
  "iterations": 100,
  "function": "TestHarness",
  "event": {
    "operation": "unit",
    "function": "HTTPSInvoker",
    "resultsTable": "unit-test-results",
    "testId": "LinuxConLoadTestDemo",
    "event": {
      "options": {
        "host": "fuexvelc41.execute-api.us-east-1.amazonaws.com",
        "path": "/prod/ImageProcessingService",
        "method": "POST"
      },
      "data": {
        "operation": "getSample"
      }
    }
  }
}

From the outside in, this JSON file

  1. Instructs load tester to run 100 copies of…
  2. The unit test, which runs the HTTPS invoker, recording the result in a DynamoDB table named “unit-test-results”
  3. The HTTPS invoker POSTs the “getSample” operation request to the API Gateway endpoint
  4. …which ultimately calls the image processing microservice, implemented as a Lambda function.

Summary

In this article we took a deeper look at serverless testing and how Lambda itself can be used as a testing platform. We saw how the test harness blueprint can be used for both unit and load testing and how the approach can be customized to the particular needs of your functions and test regimen.

Until next time, happy Lambda coding (and serverless testing)!

-Tim
Follow Tim’s Lambda adventures on Twitter