DVA-C03 Application Testing: SAM, CDK, Mocks, Test Events and Environments
Application testing for the AWS Certified Developer – Associate (DVA-C03) exam means validating code and infrastructure templates before deployment, mocking external dependencies, invoking functions with realistic JSON test events, and running each environment on exactly the version that was tested. Task 3.2 covers CDK assertion tests, template validation with cfn-lint and CloudFormation Guard, local and remote testing with the AWS SAM CLI, API Gateway mock integrations and Step Functions TestState mocks, Lambda aliases, API Gateway stages, container image tags and AWS Amplify branches as environments, and AI-assisted testing with Kiro.
On this page10 sections
- What does application testing cover in DVA-C03?
- How do you test and validate infrastructure code with CDK, CloudFormation and SAM?
- How do you test Lambda functions locally with the SAM CLI?
- How do you test deployed Lambda functions with shareable test events?
- How do you write test events that match real Lambda payloads?
- How do you mock external dependencies with API Gateway?
- How do you test Step Functions states and EventBridge patterns without running them?
- How do Lambda aliases and API Gateway stages create test environments?
- How do container image tags and Amplify branches create environments?
- How can AI tools like Kiro generate and automate tests?
- Write CDK assertion tests and validate CloudFormation and SAM templates with cfn-lint and CloudFormation Guard before deployment
- Test Lambda functions locally with the SAM CLI and in the cloud with shareable test events and sam remote invoke, knowing what each can and cannot prove
- Author JSON test events that match real API Gateway, SQS and EventBridge payloads, and test EventBridge patterns without delivering events
- Mock external dependencies with API Gateway REST mock integrations and the Step Functions TestState API
- Configure environments with Lambda aliases, API Gateway stages and stage variables, pinned container images and Amplify branches
- Use Kiro hooks, property-based tests and headless Kiro CLI to automate test execution and result analysis
What does application testing cover in DVA-C03?
Application testing in DVA-C03 Task 3.2 means proving that your code, your infrastructure templates and your environment wiring behave correctly before a change reaches users, using AWS tools that run tests quickly, locally where possible, and automatically on every change. The task has five skills, and this lesson follows them in order:
- Develop and validate tests with AWS SAM, AWS CDK and AWS CloudFormation: CDK assertion tests, template validation and linting, policy rules, and local and remote Lambda invocation.
- Mock external dependencies with AWS services: API Gateway mock integrations and the Step Functions TestState API with mocked results.
- Create test events: JSON payloads that match what Lambda really receives from API Gateway, Amazon SQS and Amazon EventBridge, and testing EventBridge event patterns.
- Create and configure environments: Lambda aliases, API Gateway stages and stage variables, container image tags and digests, and AWS Amplify branches.
- Use AI tools for testing: Kiro agent hooks that run tests, property-based tests derived from specs, and Kiro CLI in headless mode for CI.
The common thread is fidelity versus speed. A test that synthesizes a template in memory runs in seconds but cannot see IAM permissions; a test against a deployed stack is slow but real. Good pipelines use both, and many exam scenarios turn on a constraint such as "without deploying", "without invoking the function" or "with no backend code". Packaging artifacts belongs to Task 3.1, and deployment strategies, CodeDeploy and CodePipeline belong to Task 3.3.
How do you test and validate infrastructure code with CDK, CloudFormation and SAM?
You test infrastructure code by checking the synthesized template before anything is deployed: unit tests with the AWS CDK assertions module, schema checks with cfn-lint, and organisation policy checks with AWS CloudFormation Guard. Each tool answers a different question.
CDK assertion tests
The aws-cdk-lib/assertions module turns a stack into a Template object with Template.fromStack(stack). Synthesis happens in memory, so a Jest or pytest test runs in seconds with no AWS account and no deployment. Fine-grained assertions express the rules you actually care about:
hasResourceProperties(type, props)passes when at least one resource of that type has the given properties.allResourcesProperties(type, props)passes only when every resource of that type has them.resourceCountIs(type, n)checks how many resources of a type exist.Matchhelpers (such asMatch.objectLikeandMatch.arrayWith) allow partial matching.
const template = Template.fromStack(stack);
template.resourceCountIs('AWS::SNS::Topic', 3);
template.allResourcesProperties('AWS::SNS::Topic', {
KmsMasterKeyId: Match.anyValue(),
});
This test fails if someone adds a fourth topic or creates any topic without a KMS key, and it runs in a unit-test step before cdk deploy.
Snapshot tests compare the whole template with a stored copy. They break on harmless changes such as asset hashes, CDK-injected metadata and other generated values after a CDK upgrade, so teams end up accepting new snapshots without reading them. Prefer fine-grained assertions for rules; keep snapshots, if at all, as a change detector.
Validating templates before deployment
| Tool | What it checks | What it misses |
|---|---|---|
aws cloudformation validate-template / sam validate | The template is valid JSON or YAML with a correct overall structure | Misspelled property names, invalid values such as an unsupported runtime |
cfn-lint (sam validate --lint) | Resource types, property names, types and allowed values against the published resource schemas | Your organisation's own rules |
AWS CloudFormation Guard (cfn-guard validate) | Policy-as-code rules you write, for example "every S3 bucket has versioning enabled and blocks public access" | Nothing without rules: it needs a rule file |
cdk diff / CloudFormation change sets | Which resources a deployment would add, modify or replace | Whether those changes are correct: a reviewer or a test still has to judge them |
| AWS Config rules, drift detection | Resources that are already deployed | Anything before a stack is created or updated |
The template validator, cfn-lint and CloudFormation Guard return a failing exit code when a check fails, so they fit a CodeBuild test stage that stops the pipeline.
How do you test Lambda functions locally with the SAM CLI?
The AWS SAM CLI runs the functions in your template inside Docker containers that emulate the Lambda runtime, so you can invoke them with realistic events on a laptop or a CI runner before deploying.
| Command | What it does | Use it when |
|---|---|---|
sam local generate-event <service> <event> | Prints a sample payload in the same structure the service sends, for example s3 put, sqs receive-message, apigateway aws-proxy, apigateway http-api-proxy; options such as --bucket and --key change placeholder values | You need a realistic test event |
sam local invoke <LogicalId> --event file.json | Runs one function once with that event and shows its output and logs | Testing an event-driven function (S3, SQS, EventBridge) |
sam local start-api | Serves the template's API routes locally and wraps each HTTP request in an API Gateway event | Testing HTTP endpoints with curl, a browser or HTTP-level tests |
sam local start-lambda | Runs a local endpoint (default 127.0.0.1:3001) that emulates the Lambda Invoke API | An existing test suite calls functions through an AWS SDK Lambda client: change only the client's endpoint URL |
--env-vars env.json overrides environment variables for local runs; the file is keyed by each function's logical ID (or Parameters for all functions), so a local run can point at a dev table or a local stub.
What local testing cannot prove
A local invocation uses the credentials of your AWS profile, not the function's execution role. A function can therefore read and write a real DynamoDB table locally and then fail with AccessDeniedException once deployed, because SAM creates an execution role with only basic logging permissions when the function lists no Policies. Add the permissions the code needs (for example the DynamoDBCrudPolicy policy template scoped to the table). AWS notes that local testing does not validate permissions between cloud resources, which is why a good test plan also tests in the cloud.
How do you write test events that match real Lambda payloads?
A test event must have exactly the structure the triggering service sends, or the test passes while production fails. The safest starting point is sam local generate-event or the console's event templates; the traps are listed below.
API Gateway proxy events
With a Lambda proxy integration, event['body'] is a string (or null), even when the client sent JSON. A hand-written event such as {"body": {"customerId": "c-42"}}, with the body as a nested object, hides the bug; the test event's body should be a JSON-encoded string, and the handler parses it with json.loads (or JSON.parse). Binary bodies arrive Base64-encoded with isBase64Encoded set to true.
| Field | Payload format 1.0 (REST APIs, HTTP APIs set to 1.0) | Payload format 2.0 (HTTP API default) |
|---|---|---|
| HTTP method | httpMethod, requestContext.httpMethod | requestContext.http.method |
| Path | path | rawPath (and routeKey) |
| Query string | queryStringParameters, multiValueQueryStringParameters | rawQueryString, queryStringParameters |
| SAM template | apigateway aws-proxy | apigateway http-api-proxy |
A handler written and tested against 1.0 events fails with a missing-key error behind an HTTP API on 2.0. The fix is to generate fixtures with the matching template and read the 2.0 field locations (or set the integration to payload format 1.0).
Amazon SQS events
An SQS event source mapping invokes the function with a batch: {"Records": [...]}, where each record has messageId, receiptHandle, body, attributes, messageAttributes and more. The body is the message text exactly as sent, so a JSON message is a string to parse. Field names are lower camel case in the Lambda event (body, messageId), so copy a generated event rather than an SDK response when building fixtures.
Amazon EventBridge events
EventBridge delivers an envelope with version, id, detail-type, source, account, time, region, resources and the application data in detail.
How do you mock external dependencies with API Gateway?
You mock an HTTP dependency with an API Gateway REST API mock integration: API Gateway answers the request itself from mapping templates, with no backend code, so a frontend team or a test suite can work before the real service exists. HTTP APIs have no mock integration type; a stub there needs a real integration, such as a small Lambda function.
How a mock picks its response
- The integration request mapping template outputs a status code, for example
{"statusCode": 200}. Velocity (VTL) logic can branch on request data, for example emitting 503 when the query parametermodeisoutage. - API Gateway matches that status code against each integration response's HTTP status regex. The integration response with no regex is the default and is used when nothing else matches.
- The selected integration response maps to a method response with the same status and supplies the body from its own mapping template.
#if($input.params('mode') == 'outage')
{"statusCode": 503}
#else
{"statusCode": 200}
#end
Every status the mock returns therefore needs three parts: the request template that emits it, an integration response whose regex matches it (for 503, the regex 503) with a mapping template for the body, and a method response for that status. A status code that no regex matches is handled by the default integration response, so the client receives the default status and body rather than the error you meant to simulate. Gateway responses (such as DEFAULT_5XX) customise errors that API Gateway itself raises and do not take part in this selection.
How do you test Step Functions states and EventBridge patterns without running them?
You test one Step Functions state with the TestState API and an event pattern with the EventBridge TestEventPattern API; neither runs a workflow, delivers an event or invokes a target.
Step Functions TestState with mocks
TestState takes a single state's definition and an input, runs that state, and returns its output, status and next state, including the effect of InputPath, Parameters/Arguments, ResultSelector, ResultPath and OutputPath. You do not create a state machine or start an execution. Since November 2025 TestState accepts a mock for the state's service integration:
- A mocked result stands in for the integration's response, so the Lambda function or other service is never called.
- A mocked errorOutput (an error name and cause) lets you test
CatchandRetry: the response reports statusCAUGHT_ERROR, thenextStatechosen and thecatchIndex, which a test can assert on. - With a mock, the
roleArnparameter is optional, because nothing is invoked. - Activity states,
.syncand.waitForTaskTokenintegrations, and Map and Parallel states can be tested only with a mock; without one, TestState returns a validation exception, because it returns at once and cannot wait for a job or a callback.
The TRACE inspection level adds request and response detail for HTTP Task states only. ValidateStateMachineDefinition checks a definition's syntax but runs nothing.
EventBridge pattern testing
TestEventPattern (aws events test-event-pattern) takes an event pattern and a sample event and returns Result: true or false, which suits automated unit tests; the sample must include the required envelope fields id, account, source, time, region, resources (an empty array is fine) and detail-type, or the call fails validation. The EventBridge console's sandbox does the same interactively.
| EventBridge tool | What it is for |
|---|---|
TestEventPattern API / console sandbox | Unit-testing a pattern against a sample event; nothing is delivered |
PutEvents | Publishing real events, which run matching rules and invoke their targets (an integration test) |
| Archive and replay | Re-delivering past events to rules, for recovery or reprocessing |
| Schema registry | Discovering and versioning event structures and generating code bindings |
Patterns match only the fields they list, so they need neither detail-type nor every field of detail. The numeric operator compares only JSON numbers: a value sent as the string "250" never satisfies {"numeric": [">=", 100]}, and a one-sided comparison is valid.
How do Lambda aliases and API Gateway stages create test environments?
Lambda aliases and API Gateway stages let one function and one API definition serve dev, test and prod: each environment calls a stable name that points at the code approved for it.
Lambda versions and aliases
- Publishing a version freezes the function's code and configuration, including environment variables.
$LATESTis the only mutable version. - An alias (for example
invoices:live) is a named pointer to one version, with its own ARN. Integrations reference the alias, and promotion just moves the alias to the newly approved version, without changing any integration. - Aliases carry no environment variables or other configuration. When two aliases point at one version, the code and its variables are identical for both, so per-environment behaviour has to come from outside the version: the function can derive its environment from the alias suffix of the invoked function ARN (
context.invoked_function_arnends in:live) and fetch that environment's settings from Parameter Store or AWS AppConfig. - Resource-based permissions are scoped to the qualified ARN. Permission for API Gateway to invoke
invoices:stagingdoes not coverinvoices:qa; add one per alias withaws lambda add-permission --qualifier <alias>and principalapigateway.amazonaws.com. A missing permission rejects the invocation before any code runs, so the function writes no logs and API Gateway returns an internal server error.
API Gateway stages and stage variables
A REST API stage (dev, test, prod) points at a deployment, a snapshot of the API definition. Editing a method changes the working definition, which the console's Test feature uses, but no stage serves the change until you create a deployment and associate it with that stage; deploying to dev leaves prod on its older snapshot. Auto-deploy exists only for HTTP API stages.
Stage variables are per-stage name-value pairs referenced as ${stageVariables.name} in integration URLs, Lambda function ARNs and mapping templates. One definition can send the dev stage to billing-dev.corp.internal and prod to billing.corp.internal, or invoke invoices:${stageVariables.fnAlias}.
| API Gateway feature | What it controls |
|---|---|
| Stage + deployment | Which snapshot of the API definition an environment serves |
| Stage variables | Per-environment values such as backend URLs and Lambda aliases |
| Usage plans and API keys | Throttling and quotas per client |
| Base path mappings | Which API and stage a custom domain path reaches |
| Stage cache | Caching of responses for that stage |
How can AI tools like Kiro generate and automate tests?
AWS's AI development tool Kiro can run tests automatically while its agent works, generate property-based tests from spec requirements, and analyse test results unattended in CI. Each feature has a specific trigger and limit.
Agent hooks for automated test execution
A Kiro agent hook pairs a trigger with an action. The action is either a shell command (such as pytest or npm test) or an agent prompt. A File Save trigger with a pattern such as lib/**/*.ts runs tests each time the agent saves or modifies matching files; File Create can scaffold a test file for each new source file; Post Task Execution runs after the agent completes a spec task. The other triggers are Prompt Submit, Agent Stop (when the agent finishes its turn), Pre Tool Use (which runs before a change exists), Post Tool Use, Pre Task Execution, File Delete and manual triggers.
File Create, File Save and File Delete triggers respond only to changes the agent makes; your own edits in the editor do not fire them. To test manual edits too, add a test watcher, a Git pre-commit hook or a CI build.
| Kiro feature | What it is for |
|---|---|
| Agent hooks | Event-driven automation: a trigger runs a shell command or an agent prompt every time |
| Steering files | Persistent project guidance and conventions the agent reads as context |
| Specs | Requirements, design and task lists that drive implementation and property-based tests |
Property-based tests from specs
In the Kiro IDE, a spec's requirements written in EARS notation ("WHEN … THE system SHALL …") can be checked with property-based testing: Kiro extracts properties from the requirements, links each to its requirement and task, generates hundreds or thousands of random inputs and checks the code against every property. When a property fails, shrinking reduces the input to the smallest one that still fails, often a boundary value. You then fix whichever is wrong (the implementation, the test or the requirement), based on the intended behaviour. Passing properties are strong evidence, not proof, of correctness.
Kiro CLI in headless mode for CI
- Authentication uses an API key in the
KIRO_API_KEYenvironment variable, stored as a CI secret (for example in AWS Secrets Manager, referenced from the buildspec). - API keys require a paid plan, and on an administrator-managed subscription the administrator must first enable API key generation.
kiro-cli chat --no-interactiveruns one instruction without a session.--trust-toolsapproves only the tools listed (for example read-only file tools), while--trust-all-toolsapproves everything, including file edits and shell commands. Grant the narrowest list the job needs.
Typical CI uses are summarising a failing JUnit report, triaging regressions and suggesting missing tests for coverage gaps; the build's own test commands remain the source of pass or fail.
Tip. Questions on this task are developer scenarios in which a test must be written, a test is passing locally but failing in the cloud, or an environment is running the wrong code or configuration. Many are decided by a stated constraint such as without deploying, in seconds on every commit, without invoking the function, with no backend code, without rewriting the existing tests, unattended and read-only, or the same version in every environment. Others turn on a precise service behaviour: what each template-validation tool checks, which SAM CLI command emulates what, where shareable test events are stored, the exact shape of API Gateway, SQS and EventBridge payloads, how a mock integration chooses its response, when TestState needs a mock, how alias permissions are scoped, how tags and digests pin images, Amplify preview restrictions, and which Kiro trigger or CLI flag applies. Multiple-response items usually need two settings that together meet the requirement.
- CDK unit tests: Template.fromStack plus assertions, in memory, no deployment; hasResourceProperties means at least one match, allResourcesProperties means every resource.
- validate-template checks syntax, cfn-lint checks resource schemas, CloudFormation Guard checks your own policy rules; AWS Config and drift detection only see deployed resources.
- sam local invoke runs one event, start-api serves HTTP routes, start-lambda emulates the Invoke API for SDK-driven tests; local runs use your profile credentials, not the execution role.
- Shareable Lambda test events live in the lambda-testevent-schemas EventBridge schema registry and need EventBridge Schemas permissions; sam remote invoke runs them against the deployed function.
- Proxy integration bodies and SQS record bodies are strings; HTTP APIs default to payload format 2.0 (requestContext.http.method, rawPath).
- A REST API mock integration needs a request template that emits the status, a matching integration response and a method response for each status; HTTP APIs have no mock integration.
- TestState runs one state with a mocked result or error and needs no role with a mock; callback, .sync, Map and Parallel states require a mock.
- Versions freeze configuration and aliases carry none; each alias used through a stage variable needs its own invoke permission.
- Pin environments to unique immutable image tags or to digests, never to latest.
- Kiro file hooks fire only on the agent's edits; headless CI needs KIRO_API_KEY, --no-interactive and the narrowest --trust-tools list.
Frequently asked questions
What is the difference between sam local invoke, sam local start-api and sam local start-lambda?
All three run functions from a SAM template in local Docker containers. sam local invoke runs one function once with an event file, which suits event-driven functions. sam local start-api serves the template's API routes on a local HTTP endpoint and converts each request into an API Gateway event. sam local start-lambda starts a local endpoint (127.0.0.1:3001 by default) that emulates the Lambda Invoke API, so tests that already use an AWS SDK Lambda client only need their endpoint URL changed.
Why does a Lambda function work with sam local invoke but get AccessDenied after deployment?
sam local invoke runs the code with the credentials of the developer's AWS profile, so calls to real AWS resources succeed whatever the template grants. After deployment the function runs as its execution role, which SAM creates with only basic logging permissions when the function lists no Policies. Add the required permissions, such as a DynamoDBCrudPolicy policy template scoped to the table, and test in the cloud as well.
Can an API Gateway HTTP API use a mock integration?
No. Mock integrations are a REST API feature, in which API Gateway returns a response built from mapping templates without calling any backend. An HTTP API needs a real integration, such as a small Lambda function or an HTTP endpoint, to return stub responses.
How do you return an error status from an API Gateway mock integration?
Make the integration request mapping template emit the error status code, for example {"statusCode": 503}, add an integration response whose HTTP status regex matches that code with a mapping template for the error body, and add a method response for the same status. A status that no integration response regex matches is handled by the default integration response, so the client gets the default status and body instead of the error.
When must the Step Functions TestState API use a mock?
TestState returns immediately and cannot wait, so Activity states, .sync and .waitForTaskToken integrations, and Map and Parallel states can be tested only with a mocked result or error; without one it returns a validation exception. A mock also makes the execution role optional and lets a test assert on Catch and Retry handling through the returned status, nextState and catchIndex.
Why can't Lambda aliases have their own environment variables?
Environment variables belong to a function version, and publishing a version freezes them along with the code. An alias is only a pointer to a version. When several aliases point at the same version, the function can work out its environment from the alias in the invoked function ARN and load that environment's settings from Parameter Store or AWS AppConfig.
Why don't Kiro File Save hooks run when I edit files myself?
Kiro's File Create, File Save and File Delete hook triggers respond only to changes made by Kiro's agent; manual edits in the editor do not fire them. To run tests on manual changes as well, use a terminal test watcher, a Git pre-commit hook or a CI build triggered on push.
Is AWS Copilot still a good way to create test environments?
No. The DVA-C03 guide names AWS Copilot environments as an example, but the AWS Copilot CLI reached end of support on 12 June 2026 and receives no updates or security patches. For new work, create environments with ECS task definitions and image tags, Lambda aliases, API Gateway stages and AWS Amplify branches.
Source
This lesson covers the "Testing and Deployment" domain of the official DVA-C03 exam guide. Vendors revise their guides — check the source for the current version.
- AWS Certified Developer – Associate (DVA-C03) exam guide — Amazon Web Services
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.