Simulated CloudFormation
Simulated CloudFormation
Section titled “Simulated CloudFormation”Yulin includes a simulated CloudFormation service for isolated tests, local development, and CI.
Sim CloudFormation creates supported simulated AWS resources from CloudFormation templates. It can
be used with hand-written templates, AWS SDK-style CreateStackCommand calls, or synthesized CDK
template files.
The simulator focuses on useful behaviour for tests and local development rather than full CloudFormation feature parity. Unsupported resources may be skipped or may fail depending on how safely the simulator can model the requested behaviour.
Available functionality
Section titled “Available functionality”Sim CloudFormation currently supports:
- Creating stacks with
CreateStackCommand - Describing stacks with
DescribeStacksCommand - Waiting for simulated stack deployment completion
- Deploying parsed template objects with
deployTemplate(...) - Deploying synthesized JSON template files with
deployTemplateFile(...) - Template
Parameterswith supplied values and defaults - Common intrinsic functions:
RefFn::GetAttFn::JoinFn::Sub
- Explicit resource dependencies with
DependsOn - Implicit dependencies from resource
Refexpressions - Supported simulated resources, including:
AWS::CloudFormation::WaitConditionHandleAWS::S3::BucketAWS::CloudFront::Distribution- CloudFront Functions used by CDK-generated templates
- selected CDK custom resources such as CDK S3 BucketDeployment
Basic usage
Section titled “Basic usage”Create a simulated AWS environment, get simulated CloudFormation, and deploy a template.
/** * Deploying a simple CloudFormation template into simulated AWS. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();const simCfn = simAws.cloudFormation();
const stack = await simCfn.deployTemplate({ stackName: "site-stack", template: { Resources: { SiteBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: "example-site-bucket", WebsiteConfiguration: { IndexDocument: "index.html", }, }, }, }, },});
await stack.waitForDeployComplete();
const bucket = simAws.s3().getSimBucketByName("example-site-bucket");console.log(bucket?.bucketName);deployTemplate(...) returns the simulated stack object. If your test needs the created resources
to be available, wait for deployment to complete before asserting final state.
Creating stacks with AWS SDK command shapes
Section titled “Creating stacks with AWS SDK command shapes”You can also use AWS SDK-style CloudFormation commands.
/** * Creating a simulated CloudFormation Stack with CreateStackCommand. */
import { CreateStackCommand, DescribeStacksCommand,} from "@aws-sdk/client-cloudformation";
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();const simCfn = simAws.cloudFormation();
await simCfn.createStack( new CreateStackCommand({ StackName: "command-stack", TemplateBody: JSON.stringify({ Resources: { SiteBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: "command-stack-bucket", }, }, }, }), }),);
await simCfn.waitForStackDeployComplete("command-stack");
const describeOutput = await simCfn.describeStacks( new DescribeStacksCommand({ StackName: "command-stack", }),);
console.log(describeOutput.Stacks?.[0]?.StackStatus);createStack(...) starts deployment and returns once the stack has been accepted. Resource creation
continues asynchronously, similar to real CloudFormation. Use waitForStackDeployComplete(...) when
you need final stack state.
Stack deployment is asynchronous
Section titled “Stack deployment is asynchronous”A stack may be visible before all resources have finished creating.
/** * Waiting for a simulated CloudFormation deployment to finish. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
await simAws.cloudFormation().deployTemplate({ stackName: "async-stack", template: { Resources: { WaitHandle: { Type: "AWS::CloudFormation::WaitConditionHandle", }, }, },});
await simAws.cloudFormation().waitForStackDeployComplete("async-stack");You can also wait through the returned stack object:
/** * Waiting via the returned simulated CloudFormation Stack object. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const stack = await simAws.cloudFormation().deployTemplate({ stackName: "stack-object-wait", template: { Resources: { WaitHandle: { Type: "AWS::CloudFormation::WaitConditionHandle", }, }, },});
await stack.waitForDeployComplete();If your scenario also depends on asynchronous work scheduled by the created services, you can drain the broader simulator background tasks:
/** * Waiting for simulated AWS background tasks to complete. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
// Deploy stacks and interact with simulated services...
await simAws.backgroundTasksComplete();Parameters
Section titled “Parameters”Template parameters can be supplied when creating a stack.
/** * Supplying simulated CloudFormation Parameters. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const stack = await simAws.cloudFormation().deployTemplate({ stackName: "parameter-stack", template: { Parameters: { BucketName: { Type: "String", Default: "default-parameter-bucket", }, }, Resources: { SiteBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: { Ref: "BucketName", }, }, }, }, }, parameters: { BucketName: "configured-parameter-bucket", },});
await stack.waitForDeployComplete();
console.log( simAws.s3().getSimBucketByName("configured-parameter-bucket")?.bucketName,);When a parameter value is not supplied, the template default is used if present.
Intrinsic functions
Section titled “Intrinsic functions”Sim CloudFormation supports common intrinsic functions used by supported resources.
/** * Using Ref between simulated CFN resources. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const stack = await simAws.cloudFormation().deployTemplate({ stackName: "ref-stack", template: { Resources: { SourceBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: "source-ref-bucket", }, }, WebsiteBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: { "Fn::Join": ["-", [{ Ref: "SourceBucket" }, "website"]], }, }, }, }, },});
await stack.waitForDeployComplete();
console.log( simAws.s3().getSimBucketByName("source-ref-bucket-website")?.bucketName,);For supported resource types, Ref returns the resource-specific CloudFormation value. For example,
an S3 Bucket Ref returns the Bucket name.
Fn::GetAtt
Section titled “Fn::GetAtt”/** * Using Fn::GetAtt with a simulated CloudFront Distribution. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const stack = await simAws.cloudFormation().deployTemplate({ stackName: "get-att-stack", template: { Resources: { SiteBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: "get-att-site-bucket", }, }, SiteDistribution: { Type: "AWS::CloudFront::Distribution", Properties: { DistributionConfig: { Enabled: true, Origins: [ { Id: "SiteOrigin", DomainName: "get-att-site-bucket.s3.amazonaws.com", S3OriginConfig: {}, }, ], DefaultCacheBehavior: { TargetOriginId: "SiteOrigin", ViewerProtocolPolicy: "allow-all", }, }, }, }, DistributionNameHandle: { Type: "AWS::CloudFormation::WaitConditionHandle", Properties: { Value: { "Fn::GetAtt": ["SiteDistribution", "DomainName"], }, }, }, }, },});
await stack.waitForDeployComplete();For AWS::CloudFront::Distribution, Fn::GetAtt: ["Distribution", "DomainName"] returns the
simulated CloudFront hostname, such as e123example.cloudfront.net.
Fn::Join
Section titled “Fn::Join”/** * Joining literal values and Refs in a simulated CFN template. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
await simAws.cloudFormation().deployTemplate({ stackName: "join-stack", template: { Parameters: { BucketPrefix: { Type: "String", Default: "joined", }, }, Resources: { SiteBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: { "Fn::Join": ["-", [{ Ref: "BucketPrefix" }, "site", "bucket"]], }, }, }, }, },});Fn::Sub
Section titled “Fn::Sub”/** * Substituting parameter and resource values in a simulated CFN template. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const stack = await simAws.cloudFormation().deployTemplate({ stackName: "sub-stack", template: { Parameters: { SiteName: { Type: "String", Default: "docs", }, }, Resources: { SiteBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: { // eslint-disable-next-line no-template-curly-in-string "Fn::Sub": "${SiteName}-site-bucket", }, }, }, }, },});
await stack.waitForDeployComplete();
console.log(simAws.s3().getSimBucketByName("docs-site-bucket")?.bucketName);Resource dependencies
Section titled “Resource dependencies”Resources can depend on each other explicitly with DependsOn.
/** * Explicit resource dependencies in a simulated CFN template. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const stack = await simAws.cloudFormation().deployTemplate({ stackName: "depends-on-stack", template: { Resources: { SourceBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: "dependency-source-bucket", }, }, DependentBucket: { Type: "AWS::S3::Bucket", DependsOn: "SourceBucket", Properties: { BucketName: "dependency-target-bucket", }, }, }, },});
await stack.waitForDeployComplete();Resources that reference another resource with Ref are also created after the referenced resource
is ready.
Deploying synthesized CDK templates
Section titled “Deploying synthesized CDK templates”Use deployTemplateFile(...) to deploy a JSON template file, including templates produced by CDK
synthesis.
/** * Deploying a synthesized CDK template file into simulated AWS. */
import path from "node:path";
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const stack = await simAws .cloudFormation() .deployTemplateFile( path.join(process.cwd(), "cdk.out", "TestStack.template.json"), );
await stack.waitForDeployComplete();You can also pass an object when you need extra deployment options:
import path from "node:path";
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const stack = await simAws.cloudFormation().deployTemplateFile({ templatePath: path.join(process.cwd(), "cdk.out", "TestStack.template.json"), stackName: "local-cdk-stack",});
await stack.waitForDeployComplete();This is useful for local integration tests where you want CDK to produce the template, then Yulin to create the simulated resources from that synthesized output template.
CDK S3 BucketDeployment
Section titled “CDK S3 BucketDeployment”Yulin can simulate selected CDK custom resources. A common use case is CDK S3 BucketDeployment, where local files are deployed into a simulated S3 Bucket.
/** * Serving CDK BucketDeployment files through simulated S3. */
import { SimAws } from "@kensio/yulin";import { serveSimAws } from "@kensio/yulin/serve";
const simAws = new SimAws();const srv = await serveSimAws({ simAws });
try { await simAws .cloudFormation() .deployTemplateFile("cdk.out/TestStack.template.json");
const response = await fetch( `http://foo-bucket.s3-website.us-east-1.sim-aws.localhost:${srv.port}/`, );
console.log(response.status); console.log(await response.text());} finally { srv.close();}A CDK BucketDeployment can copy files from synthesized asset output into the simulated Bucket. When the Bucket is configured for website hosting, those files can be served through Yulin’s local server.
CloudFront resources from CDK
Section titled “CloudFront resources from CDK”Sim CloudFormation can create CloudFront Distributions from CloudFormation or CDK templates.
/** * Deploying a template with S3 and CloudFront resources. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const stack = await simAws.cloudFormation().deployTemplate({ stackName: "cloudfront-stack", template: { Resources: { SiteBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: "cloudfront-site-bucket", }, }, SiteDistribution: { Type: "AWS::CloudFront::Distribution", Properties: { DistributionConfig: { Aliases: ["cdn.example.test"], Enabled: true, Origins: [ { Id: "SiteOrigin", DomainName: "cloudfront-site-bucket.s3.amazonaws.com", S3OriginConfig: {}, }, ], DefaultCacheBehavior: { TargetOriginId: "SiteOrigin", ViewerProtocolPolicy: "allow-all", }, }, }, }, }, },});
await stack.waitForDeployComplete();
const distributionResource = stack.getResource("SiteDistribution");console.log(distributionResource?.simResource);When served on localhost, the resulting Distribution can be requested through its simulated CloudFront hostname adapted to the local server.
CloudFront Function bindings
Section titled “CloudFront Function bindings”When a CDK template contains a CloudFront Function, you can bind the template resource to a real local handler function. This lets local integration tests execute the same handler function that will run at the CloudFront edge.
/** * Binding a local CloudFront Function handler during template deployment. */
import { SimAws } from "@kensio/yulin";import type { CloudFrontFunction } from "@kensio/yulin/cloudfront";import { serveSimAws } from "@kensio/yulin/serve";
const simAws = new SimAws();const srv = await serveSimAws({ simAws });
function viewerRequestHandler( event: CloudFrontFunction.ViewerRequestEvent,): CloudFrontFunction.Request | CloudFrontFunction.Response { if (event.request.uri === "/redirect-me.html") { return { statusCode: 302, statusDescription: "Found", headers: { location: { value: "https://example.test/from-bound-handler.html", }, }, }; }
return event.request;}
try { const stack = await simAws.cloudFormation().deployTemplateFile({ templatePath: "cdk.out/TestStack.template.json", bindings: [ { logicalId: "RewriteFunction", handler: viewerRequestHandler, }, ], });
await stack.waitForDeployComplete();
const distributionResource = stack.getResource("SiteDistribution"); const distribution = distributionResource?.simResource;
if ( distribution === undefined || !("distributionId" in distribution) || typeof distribution.distributionId !== "string" ) { throw new Error("Expected simulated CloudFront Distribution"); }
const distributionHost = `${distribution.distributionId.toLowerCase()}.cloudfront.net`; const response = await fetch( srv.localUrl(`http://${distributionHost}/redirect-me.html`), { redirect: "manual" }, );
console.log(response.status); console.log(response.headers.get("location"));} finally { srv.close();}The bindings array matches a template resource logical ID to a local handler function. This is
especially useful when CDK has embedded or transformed CloudFront Function source in synthesized
output, but your test wants to provide an executable local function directly.
Serving deployed resources on localhost
Section titled “Serving deployed resources on localhost”CloudFormation itself is not served as an HTTP API. Instead, you deploy infrastructure through Sim
CloudFormation, then serve the simulated AWS environment with serveSimAws.
/** * Deploy with sim CloudFormation, then serve the simulated resources on localhost. */
import { SimAws } from "@kensio/yulin";import { serveSimAws } from "@kensio/yulin/serve";
const simAws = new SimAws();const srv = await serveSimAws({ simAws });
try { const stack = await simAws.cloudFormation().deployTemplate({ stackName: "local-site-stack", template: { Resources: { SiteBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: "local-site-bucket", WebsiteConfiguration: { IndexDocument: "index.html", }, }, }, }, }, });
await stack.waitForDeployComplete();
await simAws.s3().putObject({ input: { Bucket: "local-site-bucket", Key: "index.html", Body: "<h1>Hello from Sim CloudFormation</h1>", ContentType: "text/html; charset=utf-8", }, });
const websiteUrl = simAws.s3().getBucketWebsiteUrl("local-site-bucket"); const response = await fetch(srv.localUrl(websiteUrl));
console.log(response.status); console.log(await response.text());} finally { srv.close();}Use srv.localUrl(...) to adapt simulated service URLs to the local server while preserving the
simulated hostname and service routing information.
Accounts and Regions
Section titled “Accounts and Regions”Use SimAws scopes to create stacks in different simulated Accounts and Regions.
/** * Deploying stacks in different simulated Accounts and Regions. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const defaultCfn = simAws.cloudFormation();const euWest2Cfn = simAws.region("eu-west-2").cloudFormation();const accountCfn = simAws.account("111111111111").cloudFormation();const scopedCfn = simAws .account("222222222222") .region("ap-east-1") .cloudFormation();
await defaultCfn.deployTemplate({ stackName: "default-stack", template: { Resources: { DefaultHandle: { Type: "AWS::CloudFormation::WaitConditionHandle", }, }, },});
await euWest2Cfn.deployTemplate({ stackName: "regional-stack", template: { Resources: { RegionalHandle: { Type: "AWS::CloudFormation::WaitConditionHandle", }, }, },});
await accountCfn.deployTemplate({ stackName: "account-stack", template: { Resources: { AccountHandle: { Type: "AWS::CloudFormation::WaitConditionHandle", }, }, },});
await scopedCfn.deployTemplate({ stackName: "scoped-stack", template: { Resources: { ScopedHandle: { Type: "AWS::CloudFormation::WaitConditionHandle", }, }, },});Stacks are scoped to the selected simulated account and region. Resources created by a stack are created through that same simulated account/region scope unless the underlying simulated service has different AWS-like scoping behaviour.
Inspecting stacks and resources
Section titled “Inspecting stacks and resources”After deployment, you can inspect the returned stack and its resources.
/** * Inspecting resources created by a simulated CloudFormation Stack. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const stack = await simAws.cloudFormation().deployTemplate({ stackName: "inspect-stack", template: { Resources: { SiteBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: "inspect-site-bucket", }, }, }, },});
await stack.waitForDeployComplete();
const bucketResource = stack.getResource("SiteBucket");
console.log(bucketResource?.simResource);This is useful in tests when you want to assert that a specific template resource created the expected simulated service resource.
Handling deployment failures
Section titled “Handling deployment failures”Some deployment failures happen asynchronously after stack creation has started. To observe those failures in tests, wait for deployment completion.
/** * Observing simulated CloudFormation deployment failures. */
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
const stack = await simAws.cloudFormation().deployTemplate({ stackName: "failure-stack", template: { Resources: { InvalidBucket: { Type: "AWS::S3::Bucket", Properties: { BucketName: "Invalid_Bucket_Name", }, }, }, },});
try { await stack.waitForDeployComplete();} catch (error) { console.error("Stack deployment failed", error);}If you use waitForStackDeployComplete(...), deployment errors are also rethrown there.
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();
// Create a stack...
try { await simAws.cloudFormation().waitForStackDeployComplete("failure-stack");} catch (error) { console.error("Stack deployment failed", error);}Supported resources and limitations
Section titled “Supported resources and limitations”Sim CloudFormation supports a focused subset of CloudFormation.
Current supported resource areas include:
AWS::CloudFormation::WaitConditionHandleAWS::S3::BucketAWS::CloudFront::Distribution- selected CloudFront Function resources emitted by CDK
- selected CDK custom resources, including CDK S3 BucketDeployment
Common template features currently supported include:
ParametersRefFn::GetAttFn::JoinFn::SubDependsOn
Notable limitations:
TemplateBodymust be JSON when usingCreateStackCommand; YAML parsing is not currently provided by the CloudFormation service.- Only supported resource types create simulated service resources.
- Unsupported resource properties may be ignored or rejected depending on the resource simulator.
- Stack updates and deletes are not currently documented as supported operations.
- Outputs, mappings, conditions, and many advanced CloudFormation features are not currently documented as supported operations.
For best results, keep test templates focused on the resources and behaviours your application actually needs.
Standalone SimCloudFormation
Section titled “Standalone SimCloudFormation”Most users should access CloudFormation through SimAws so that CloudFormation can create resources
in the same simulated AWS environment as S3, CloudFront, and other services.
import { SimAws } from "@kensio/yulin";
const simAws = new SimAws();const simCfn = simAws.cloudFormation();SimCloudFormation is also exported from @kensio/yulin/cloudformation for advanced cases. In
normal application tests, prefer the SimAws entry point.
