
Once the AWS Lambda code has been tested in isolation, we need to make sure that it works together with the other services it interacts with. Some functionalities can be delegated entirely to managed services such as Amazon S3, or created without using any internally developed code. There is no need to test these managed services, but you do need to test the integration with these services.
One way to test the integration with cloud managed services is using Emulator, a locally running application which mimics a production AWS service.
Emulators have APIs that are similar to their cloud counterparts and provide similar return values. They can also simulate state changes that are initiated by API calls. For example, you might use AWS SAM to run a function with AWS SAM local to emulate the Lambda service so that you can quickly invoke a function. See AWS SAM local in the AWS Serverless Application Model Developer Guide for details.
The advantages of test with emulators include the following:
Testing with emulators has these disadvantages:
You can run integration tests against cloud-based code that also interacts with cloud-based services.
Although the test runs locally, it interacts with cloud-based resources. These resources have been deployed using the AWS Serverless Application Model and AWS SAM command line tool.
To demonstrate how we can implement and run Integration Tests in the cloud for the TicketFunction, we will use the code in TicketFunctionIntegrationTest.java.
This tests assumes that the DynamoDB table is provisioned in your AWS account,
and you have the appropriate credentials locally to access this account (for example in ~/.aws/credentials).
To do that, you need first to deploy the SAM Template provided in the repository using the commands
sam build and sam deploy as follows:
$ sam build
...
Build Succeeded
$ sam deploy --guided
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Found
Reading default arguments : Success
Setting default arguments for 'sam deploy'
=========================================
Stack Name [apigw-lambda-ddb]: apigw-lambda-ddb
AWS Region [us-east-2]: us-east-2
....
Make sure to specify the AWS Region us-east-2 as TicketFunctionIntegrationTest.java assumes that the DynamoDb table is deployed in us-east-2
So far we have seen how to write unit tests by mocking classes we defined to wrap API calls to AWS Services such as DynamoDB. In an integration test, you still will mock the event payload but instead of mocking the DynamoDB APIs, you will actually call the real APIs on cloud.
Our integration tests then can feed custom input events to our Lambda Function and test the behavior in real as we are
calling the actual API to AWS Services.
It is the best practice to define these input events as json files stored in the src/test/resources/events folder.
We will use the aws-lambda-java-tests library to seamlessly load and deserialize JSON events and inject them in your tests.
The @Event annotation provides loaders for the most common event types available in aws-lambda-java-events.
It requires the path of a json file with the event definition from the src/test/resources folder,
and the aws-lambda-java-events type to map the event with.
Similar to unit tests, we use the JUnit5 ParameterizedTest annotation to inject arguments into a test,
so we can run the same test one or more times with different parameters.
The major difference in integration tests compared to unit tests is
that the actual DynamoDB database on AWS will be used in the test.
Therefore,
make sure the AWS credentials used in the tests are valid and a DynamoDBClient is available in the same region as the
database for assertion.
@ExtendWith(SystemStubsExtension.class)
public class TicketFunctionIntegrationTest {
private static final Region TEST_REGION = Region.US_EAST_2;
@SystemStub
private static EnvironmentVariables environmentVariables;
private final DynamoDbClient ddbClient = DynamoDbClient.builder()
.region(TEST_REGION)
.build();
private List<String> ticketList = new ArrayList<>();
@BeforeAll
public static void setup() {
ProfileCredentialsProvider credentialsProvider = ProfileCredentialsProvider.create("default");
AwsCredentials awsCredentials = credentialsProvider.resolveCredentials();
environmentVariables.set("AWS_ACCESS_KEY_ID", awsCredentials.accessKeyId());
environmentVariables.set("AWS_SECRET_ACCESS_KEY", awsCredentials.secretAccessKey());
environmentVariables.set("AWS_REGION", TEST_REGION.id());
}
@ParameterizedTest
@Event(value = "events/apigw_request_1.json", type = APIGatewayProxyRequestEvent.class)
public void testPutTicket(APIGatewayProxyRequestEvent event, EnvironmentVariables environmentVariables) {
TicketFunction function = new TicketFunction();
// Note there is no mocking API used before calling the handler
APIGatewayProxyResponseEvent response = function.handleRequest(event, null);
Assertions.assertNotNull(response);
Assertions.assertNotNull(response.getBody());
String uuidStr = response.getBody();
Assertions.assertNotNull(uuidStr);
ticketList.add(uuidStr.substring(1, uuidStr.length() - 1));
DynamoTestUtil.validateItems(ticketList, ddbClient);
}
}
You can find the complete integration test code for this example in TicketFunctionIntegrationTest.java and all the other examples in the same GitHub repository serverless-test-samples.