Static analysis – Testing Serverless Applications

Static analysis

If you are using a language and runtime that support static typing, you can leverage static analysis as a method to verify the requests sent to a third party and your handling of their responses.

Static analysis is the process of verifying software without execut‐ ing it.

Third parties will often provide official type definitions in various programming languages. These type definitions should be applied to operations in your Lambda functions that involve sending API requests and handling responses. Provided the type definitions are correct and synchronized with the version of the API you are using, you can assume that the request you send will be accepted and produce the expected result:

import { PaymentRequest, PaymentResponse } from “@payment/api”

const handler = async () => {

const response: PaymentResponse = await fetch(“https://pay.co/api/payment”, {

body: JSON.stringify({ “amount”: 100 } as PaymentRequest),

method: “POST”,

});

return response.paymentId;

};

Aside from some specific scenarios, there is usually no need to make the API request to verify your integration with a third party. The type definitions represent your data contract with the third-party vendor. Figure 7-5 shows how this might look as a conversation.

Figure 7-5. Contracts can be established between you and your third-party providers through request and response schemas and enforced in your codebase with type defini tions and static analysis

Contract Testing Integration Points

The typical approach to testing the integration between two or more components (or microservices) in a system involves deploying the components, making a request to an entry point, such as an API endpoint, to trigger the integrated process, and asserting on the intermediate and ultimate state of the components.

This integration testing strategy usually requires the creation and maintenance of a complex delivery pipeline and produces brittle test suites that couple the decoupled components under test. While there may be scenarios where this approach makes sense, it will probably generate far too much overhead to be valuable.

Testing of the Cloud

One additional consideration for serverless applications is the role of managed serv‐ ices. The business logic components in your application will most likely be integra‐ ted with a managed service. Where integration points involve managed services, it becomes necessary to consider the remit of your operational and, by extension, your testing responsibility. You should only be testing the code you are responsible for and make sure you are not testing AWS. It can be useful to keep in mind the mantra, “If you can’t fix it you shouldn’t test it.”

The shared responsibility model (covered in Chapter 4) for cloud security can be extended to provide guidance on where to draw the boundaries of application testing. The responsibility of AWS can broadly be described as testing of the cloud. Take the example of an EventBridge rule. It is your responsibility to configure the custom event bus that will receive events (unless you’re using the default event bus), the event pattern to match against incoming events, and the target to trigger when matching events are received by the bus. AWS will operate the event bus, accept incoming events, analyze events for matching patterns, and trigger the corresponding targets.

You are responsible for the configuration of managed services and AWS is responsible for their operation. To preserve this boundary in your tests, you should be able to test this configuration without the need to invoke the underlying services.

Instead of testing integration points by deploying and invoking the integrated micro‐ services and managed services, you can use data contracts to verify the correctness of integrations.

You may have encountered contract testing before. The prevalent approach to contract testing involves the use of the Pact frame‐ work. While you may choose to use such a framework, it is important to distinguish between the principle of contract testing (statically testing requests and responses against agreed data types) and the implementation of contract testing via frameworks such as Pact.

This chapter explores contract testing as a form of unit testing, without the use of any additional frameworks beyond standard primitives like JSON Schema.

In the context of a serverless application on AWS, a data contract can exist between any distinct resources that are connected by an asynchronous (e.g., event or mes‐ sage) or synchronous (e.g., API request) communication. A data contract could be enforced to verify the correctness of an integration for SQS messages, API Gateway integrations, Step Functions inputs, DynamoDB operation payloads, and so on.

As highlighted earlier in this section, for each integration point there are usually three elements to test: permissions, payloads, and configuration. Let’s look at an example for each of these elements based on the reference architecture described in Figure 7-4.

Leave a Reply

Your email address will not be published. Required fields are marked *