Testing resource types using contract tests
As you model and develop your resource type, you should have the CloudFormation CLI perform tests to ensure that the resource type is behaving as expected during each event in the resource lifecycle. The CloudFormation CLI performs a suite of tests called contract tests to enforce CloudFormation’s handler contract. Developing and registering your resource type in CloudFormation signifies an agreement that your resource is compliant and doesn't break any framework expectations. All resources that fail contract tests are blocked from publishing into our registry.
Important
A new version of the contract tests is available through the --v2 flag of
the cfn test command. Going forward, all newly registered resource types
must pass contract tests v2 during registration. We recommend that you adopt
cfn test --v2 early to identify and resolve any gaps, though
cfn test without the flag continues to work as before. To adopt the new
tests, you might need to update your test input files in addition to your handler code.
The --v2 flag supports only resource types developed for the Java runtime.
For other languages, such as TypeScript and Go, cfn test remains
functional.
Testing resource types locally using AWS SAM
Once you've implemented the desired handlers for your resource, you can also test the resource locally using the AWS SAM command line interface (CLI), to make sure your resource behaves as expected, debug what's wrong, and fix any issues.
To start testing, use the AWS SAM CLI to start the Local Lambda service. Run the following command in a terminal separate from your resource type workspace, or as a background process.
$sam local start-lambda
If you have functions defined in your AWS SAM template, it will provide an endpoint to invoke these functions locally. This is especially helpful because it allows for remote debugging to step through resource type invocations in real time.
Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint. 2020-01-15 15:27:19 * Running on http://127.0.0.1:3001/ (Press CTRL+C to quit)
Alternatively, you can also specify using the public Lambda service and invoke
functions deployed in your account. Be aware, however, that using the local service
allows for more iteration. To specify a debug port for remote debugging, use the
-d option:
$sam local start-lambda -d PORT
Once you have the Lambda service started, use the test command to perform contract tests:
$cfn test
The CloudFormation CLI selects the appropriate contract tests to execute, based on the handlers specified in your resource type schema. If a test fails, the CloudFormation CLI outputs a detailed trace of the failure, including the related assertion failure and mismatched values.
For more information about testing using AWS SAM CLI, see Testing and debugging serverless applications in the AWS Serverless Application Model Developer Guide.
Testing resource types using the --v2 flag
Use the --v2 flag of the cfn test command to run the new
version of the contract tests in a Docker container. Unlike the default
cfn test command, cfn test --v2 doesn't require AWS SAM or a
running local Lambda endpoint for resource types using the Java runtime. The test runner
loads your handler package directly. The same test suite runs during type registration,
so a type that passes locally will pass at registration.
Prerequisites
-
Docker installed and running. The
--v2flag is supported on macOS and Linux. -
A built handler package (
<my-resource-type>.zip) in your project root — for example, one created by runningcfn submit --dry-run. -
Valid AWS credentials and an AWS Region. Contract tests provision real resources in your account.
-
Outbound network access to the Amazon ECR Public Gallery, so the CloudFormation CLI can download the test runner image.
To run the tests, from your project root:
$cfn test --v2
The CloudFormation CLI verifies the prerequisites, pulls the latest test runner image
from the Amazon ECR Public Gallery, and runs the tests against your handler package.
Test results stream to your terminal as tests execute. The CloudFormation CLI writes
detailed HTML and JUnit XML reports to the rqts-output/ directory in your
project root. The command exits with code 0 when all tests pass.
If the image can't be downloaded and a previously downloaded copy is available locally, the CloudFormation CLI runs that copy and warns that it might be out of date.
As with the default command, the CloudFormation CLI selects the appropriate tests based
on the handlers and properties specified in your resource type schema. The
CloudFormation CLI reads test input from the files in the inputs directory
packaged in your handler package. The same inputs_1_create.json /
inputs_1_update.json / inputs_1_invalid.json conventions
apply. Note that overrides.json is not used by --v2 — specify
input data using input files.
The following options are supported with --v2.
| Option | Description |
|---|---|
|
|
The Region used for temporary credentials and resource provisioning. |
|
|
The AWS profile used for temporary credentials. |
|
|
The IAM role assumed when performing handler operations. |
|
|
Values passed when assuming the role, matching the trust policy conditions of generated execution roles. |
|
|
The type configuration file supplying additional input to the handler. |
Other cfn test options, including --endpoint,
--function-name, --log-group-name,
--log-role-arn, and --docker-image, apply only to the
default command and have no effect when you specify --v2. Arguments passed
through to pytest after -- are likewise not used.
Note
Results can differ from registration if your local test runner image is older than the image used at registration, or when handler behavior depends on the Lambda execution environment.
For more information, see Contract tests performed with the --v2 flag.
Troubleshooting
The following entries describe common failures and how to resolve them.
cfn test --v2fails with a command not found error-
Your installed version of the CloudFormation CLI doesn't include the
--v2flag. Upgrade the CloudFormation CLI to a version that supports contract tests v2, and then run the command again. - Tests fail with
the RQTS local test runner supports Java projects only -
Your resource type isn't developed for the Java runtime. The
--v2flag supports only Java resource types, so resource types developed for other languages can't be tested locally. - Tests fail during setup with an expired token error
-
Contract tests use your existing credentials if they're already temporary credentials, so an expired session isn't detected until the tests begin provisioning resources. Refresh your credentials and run the command again.
- Tests fail with
AlreadyExists -
A previous run that ended early can leave resources behind in your account. Because input files typically specify fixed resource names, later runs fail when the
createhandler is invoked. Delete the resources that the previous run created, and then run the command again. To make the suite repeatable, use unique resource names in your input files.
How the CloudFormation CLI constructs and executes contract tests
The CloudFormation CLI uses PyTest
The tests themselves are located in the suitepytest markers. For example, tests applicable
to the create handler are adorned with the @pytest.mark.create
marker. This enables the CloudFormation CLI to execute only those tests appropriate for a
resource type, based on the handlers specified in the resource type's schema. For
example, suppose a resource type's schema specified create,
read, and delete handlers. In this case, the
CloudFormation CLI would not perform any test marked with only the
@pytest.mark.update or @pytest.mark.list, since those
handlers weren't implemented.
To test create and update handlers, the CloudFormation CLI
uses the resource type's schema and Hypothesis
Tests create, update, and delete resources to test various aspects of the resource
handler contract during handler operations. The CloudFormation CLI uses PyTest
fixtures to decrease the amount of time the contract tests take to
perform. Using fixtures enable the tests within a test module to share resources, rather
than have to create a new resource for each test. Currently, the contract tests employ
the following fixtures:
-
created_resourcein thehandler_createtest module. -
updated_resourcein thehandler_updatetest module. -
deleted_resourceinhandler_deletetest module.
Specifying input data for use in contract tests
By default, the CloudFormation CLI performs resource contract tests using input properties generated from the patterns you define in your resource type schema. However, most resources are complex enough that the input properties for creating or updating those resources requires an understanding of the resource being provisioned. To address this, you can specify the input the CloudFormation CLI uses when performing its contract tests.
The CloudFormation CLI offers two ways for you to specify the input data for it to use when performing contract tests:
-
Overrides file
Using an
overridesfile provides a light-weight way of specifying input data for certain specific properties for the CloudFormation CLI to use during both create and update operations testing. -
Input files
You can also use multiple
inputfiles to specify contract test input data if:-
You want or need to specify different input data for create and update operations, or invalid data with which to test.
-
You want to specify multiple different input data sets.
-
Specifying input data using an override file
Using an override file enables you to overwrite input values for specific resource properties. Input values specified in the override file are used in contract testing for both create and update operations. You can only specify a single override file, and only specify a single input value for each resource property. For any properties for which you don't specify values, the CloudFormation CLI uses generated input.
Because the input data specified in the overrides.json file is used
by the CloudFormation CLI during testing of create and update operations, you can't
include input values for create-only properties in the file, as this would lead to
contract test failures during update operations. For more information, see createOnlyProperties.
To override the input data used for specific properties during contract testing,
add an overrides.json file to the root directory of your resource type
project. The overrides.json file should contain only the resource
properties to be used in testing. Use the following syntax:
{ "CREATE": { "property_name": "property_value" # optional_comment } }
For example:
{ "CREATE": { "SubnetId": "subnet-0bc6136e" # This should be a real subnet that exists in the account you're testing against. } }
You can also use output values from other stacks when specifying input data. For
example, suppose you had a stack that contained an export value named
SubnetExport:
Resources: VPC: Type: "AWS::EC2::VPC" Properties: CidrBlock: "10.0.0.0/16" Subnet: Type: "AWS::EC2::Subnet" Properties: CidrBlock: "10.0.0.0/24" VpcId: !Ref VPC Outputs: SubnetId: Value: !Ref Subnet Export: Name: SubnetExport
You could then specify that export value as input data using the export value name using the following syntax:
{ "CREATE": { "SubnetId": "{{SubnetExport}}" } }
For more information, see Outputs.
Specifying input data using input files
Use input files to specify different kinds of input data for the
CloudFormation CLI to use: create input, update input, and invalid input. Each kind
of data is specified in a separate file. You can also specify multiple sets of input
data for contract tests.
To specify input files for the CloudFormation CLI to use in contract
testing, add an inputs folder to the root directory of your resource
type project. Then add your input files.
Specify which kind of input data a file contains by using the following naming
conventions, where n is an
integer:
-
inputs_: Use files withn_create.json_create.jsonfor specifying inputs for creating the resource. This includes input values for create-only properties. For more information, see createOnlyProperties. -
inputs_: Use files withn_update.json_update.jsonfor specifying inputs for updating the resource. -
inputs_: Use files withn_invalid.json_invalid.jsonfor specifying invalid inputs to test when creating or updating the resource.
To specify multiple sets of input data for contract tests, increment the integer
in the file names to order your input data sets. For example, your first set of
input files should be named inputs_1_create.json,
inputs_1_update.json, and inputs_1_invalid.json. Your
next set would be named inputs_2_create.json,
inputs_2_update.json, and inputs_2_invalid.json, and
so on.
Each input file is a JSON file containing only the resource properties to be used in testing. Below is an example of an input file data set.
{ "AlarmName": "Name", "AlarmDescription": "TestAlarmDimensions Description", "Namespace": "CloudWatchNamespace", "MetricName": "Fault", "Dimensions": [ { "Name": "MethodName", "Value": "Value" } ], "Statistic": "Average", "Period": 60, "EvaluationPeriods": 5, "Threshold": 0.01, "ComparisonOperator": "GreaterThanOrEqualToThreshold" }
If you specify an inputs folder, the CloudFormation CLI uses only the
input data included in that folder. Therefore, you must specify create, update, and
invalid data files for the CloudFormation CLI to successfully complete the contract
tests.
If you specify both input files and an overrides files, the CloudFormation CLI ignores
the overrides file and uses the input data specified in the inputs
folder.
You can also use output values from other stacks when specifying input data. For
example, suppose you had a stack that contained an export value named
SubnetExport:
Resources: VPC: Type: "AWS::EC2::VPC" Properties: CidrBlock: "10.0.0.0/16" Subnet: Type: "AWS::EC2::Subnet" Properties: CidrBlock: "10.0.0.0/24" VpcId: !Ref VPC Outputs: SubnetId: Value: !Ref Subnet Export: Name: SubnetExport
You could then specify that export value as input data using the export value name using the following syntax:
{ "SubnetId": "{{SubnetExport}}", . . . }
For more information, see Outputs.
Viewing contract test logs
It's important to note that contract tests aren't run during private type registration
(unless the resource contains one of the following namespaces: aws,
amzn, alexa, amazon,
awsquickstart), but failing contract tests does block a publisher's
ability to publish their type. This is because public resources are consumed by other
external customers and need to maintain a high quality bar. For this reason, it's
important to debug your contract test failures early on in the resource development
process.
Running contract tests generates two types of logs. Using both helps expedite the debugging process.
-
Lambda logsshow logs from your handlers and provide more details on the input and output for each handler call. -
Test logsshow the result of running the test suite, including which tests have failed or passed, in addition to a traceback if a test has failed.
Because there are multiple ways to invoke contract tests against your resource, there are different places to find logs depending on which operation you are using.
-
If you run contract tests locally, logs are divided into the following two sections:
-
Lambda logs are in the terminal tab in which you ran
sam local start-lambda. -
Test logs are in the terminal tab in which you ran
cfn test.
-
-
If you run contract tests through the type registration (
cfn submit), logs are uploaded in two areas. Contract tests are only run during type registration if your type name includes one of the following namespaces:aws,amzn,alexa,amazon,awsquickstart.-
Lambda logs are delivered to a CloudWatch log group in your account. The log group adheres to the following naming pattern:
<Hyphenated TypeName>-ContractTests-<RegistrationToken>For example,
aws-cloudwatch-alarm-ContractTests-ca7096d7-ccb3-4c7d-ad51-78d0a1a300ca. -
To receive test logs in an Amazon S3 bucket, you have to modify the IAM role that's created by CloudFormation, which adheres to the following naming pattern:
CloudFormationManagedUplo-LogAndMetricsDeliveryRol-<RandomId>Add the following inline policy:
Also add the following trust policy:
Invoking
cfn submit --role-arn <arn for above IAM role>uploads your test logs to an Amazon S3 bucket namedcloudformationmanageduploadinfrast-artifactbucket-<RandomId>under the following path:CloudFormation/ContractTestResults/<TypeName>/<ContractTestInvocationToken>.zipDownload the zip file to see your test logs.
-
-
If you run contract tests against your registered type through the
TestType, both logs are condensed and uploaded to an Amazon S3 bucket in your account. You must specify the--log-delivery-bucketparameter when invokingTestTypeto receive logs in your account.
Testing resource types manually
Running contract tests with the cfn-test command uses the AWS SAM CLI, so
it's possible to attach a debugger from your IDE by specifying a port when you start the
local Lambda service. However, we don't suggest this approach because the debugger
detaches after each individual handler invocation completes.
Instead, you can mimic the scenarios modeled in contract tests by invoking the
handlers with the sam local invoke command. This allows you to step through
each handler invocation without interruption. You first need to define test templates
that AWS SAM can run against the resource handlers. Create the test templates in a
separate folder in the resource directory and name the folder
sam-tests.
The test templates must adhere to the following format:
{ "credentials": { "accessKeyId": "<Access Key Id>", "secretAccessKey": "<Secret Access key>", "sessionToken": "<Session Token>" }, "action": "<Action>", "request": { "clientRequestToken": "<Random UUID>", "desiredResourceState": <ResourceModel json>, "logicalResourceIdentifier": "<Logical Id>" }, "callbackContext": <CallbackContext json> }
-
For
credentials, use temporary credentials for an IAM role (such asPowerUserAccessorDeveloper) in your personal AWS account. Replace theaccessKeyId,secretAccessKey, andsessionTokenwith their corresponding values. For instructions on how to copy IAM role credentials from the AWS access portal, see Manual credential refresh in the AWS IAM Identity Center User Guide. The settings for the IAM role you choose determine how long the temporary credentials are valid.Using the AWS CLI, you can call an AWS STS API like
AssumeRoleorGetFederationTokenand then capture the resulting output. For more information, see Using temporary credentials with AWS resources in the IAM User Guide.Note
In the past, it was common practice to use persistent credentials, such as IAM user credentials or even root credentials, but this is not recommended. For more information, see Security best practices in IAM in the IAM User Guide.
-
For
action, specify the handler you want to test. Allowed values:CREATE,READ,DELETE,UPDATE,LIST. -
For
clientRequestToken, specify a random UUID string. To retrieve this, use any UUID generator tool. -
For
desiredResourceState, specify the properties of the resource required for the request that follow the resource schema. -
For
logicalResourceIdentifier, specify a logical ID to assign to your resource instance. You can use this in subsequent handler invocations for the same resource. -
For
callbackContext, the request begins with a null value for callback context. For handlers with stabilization logic, the subsequent requests have callback context from the previous request's response.
Once you've written the input files, do the following to debug your handlers:
-
Ensure that Docker is downloaded and installed on your machine, and that you've added the resource directory to Docker.
-
In one terminal, start the local Lambda service by running
sam local start-lambda. -
In your IDE, create a remote configuration and add a port number. Add a breakpoint in the appropriate handler you are invoking.
-
In another terminal, invoke the handler by running
sam local invoke TestEntrypoint --event sam-tests/<input filename> -d <PORT number>. -
Step through the code to debug any handler errors.
For more information about testing using the AWS SAM CLI, see Testing and Debugging Serverless Applications in the AWS Serverless Application Model Developer Guide.