Local Testing with LocalStack
Test your FsCDK infrastructure locally without deploying to AWS using LocalStack.
What is LocalStack?
LocalStack emulates AWS services on your local machine, enabling: - Fast iteration without AWS deployment delays - Zero AWS costs during development - Offline development and testing - Integration tests in CI/CD pipelines
Quick Setup
1. Install LocalStack
|
2. Install CDK Local Wrapper
|
3. Start LocalStack
|
Using FsCDK with LocalStack
Your FsCDK code works unchanged! Just use cdklocal instead of cdk:
|
Example: Testing a Lambda Function
#r "../src/bin/Release/net8.0/publish/Amazon.JSII.Runtime.dll"
#r "../src/bin/Release/net8.0/publish/Constructs.dll"
#r "../src/bin/Release/net8.0/publish/Amazon.CDK.Lib.dll"
#r "../src/bin/Release/net8.0/publish/System.Text.Json.dll"
#r "../src/bin/Release/net8.0/publish/FsCDK.dll"
open FsCDK
open Amazon.CDK
open Amazon.CDK.AWS.Lambda
open Amazon.CDK.AWS.DynamoDB
// Define your stack normally
stack "LocalTestStack" {
description "Stack for local testing with LocalStack"
table "Users" {
partitionKey "userId" AttributeType.STRING
billingMode BillingMode.PAY_PER_REQUEST
}
lambda "UserFunction" {
runtime Runtime.DOTNET_8
handler "Handler::process"
code "./publish"
environment [ "TABLE_NAME", "Users" ]
}
}
Deploy to LocalStack:
|
Test your function:
|
Integration Testing Example
Use LocalStack in your test suite:
open Expecto
open System.Diagnostics
[<Tests>]
let integrationTests =
testList "LocalStack Integration" [
test "can deploy and invoke lambda" {
// Deploy to LocalStack
let deploy = Process.Start("cdklocal", "deploy --require-approval never")
deploy.WaitForExit()
Expect.equal deploy.ExitCode 0 "Deployment should succeed"
// Test your resources via AWS CLI with local endpoint
// ...
}
]
Configuration
LocalStack uses standard AWS environment variables:
|
Supported Services
LocalStack supports 80+ AWS services. Most FsCDK resources work including: - Lambda, DynamoDB, S3, SQS, SNS - API Gateway, EventBridge, Step Functions - VPC, EC2, RDS (basic functionality) - CloudWatch, IAM, Secrets Manager
Check LocalStack coverage for specific service support.
Limitations
- Some advanced features may not be fully emulated
- Performance characteristics differ from real AWS
- Not a replacement for staging environment testing
- Use for local development and unit/integration tests
CI/CD Integration
Add LocalStack to GitHub Actions:
|
Resources
Pro Tip: Start with LocalStack for rapid iteration, then verify in a real AWS dev account before production deployment.
<summary>Creates an AWS CDK Stack construct.</summary>
<param name="name">The name of the stack.</param>
<code lang="fsharp"> stack "MyStack" { lambda myFunction bucket myBucket } </code>
<summary>Sets the stack description.</summary>
<param name="config">The current stack configuration.</param>
<param name="desc">A description of the stack.</param>
<code lang="fsharp"> stack "MyStack" { description "My application stack" } </code>
<summary>Creates a DynamoDB table configuration.</summary>
<param name="name">The table name.</param>
<code lang="fsharp"> table "MyTable" { partitionKey "id" AttributeType.STRING billingMode BillingMode.PAY_PER_REQUEST } </code>
<summary>Sets the partition key for the table.</summary>
<param name="config">The current table configuration.</param>
<param name="name">The attribute name for the partition key.</param>
<param name="attrType">The attribute type (STRING, NUMBER, or BINARY).</param>
<code lang="fsharp"> table "MyTable" { partitionKey "id" AttributeType.STRING } </code>
<summary>Sets the billing mode for the table.</summary>
<param name="config">The current table configuration.</param>
<param name="mode">The billing mode (PAY_PER_REQUEST or PROVISIONED).</param>
<code lang="fsharp"> table "MyTable" { billingMode BillingMode.PAY_PER_REQUEST } </code>
<summary>Creates a Lambda function configuration.</summary>
<param name="name">The function name.</param>
<code lang="fsharp"> lambda "MyFunction" { handler "index.handler" runtime Runtime.NODEJS_18_X code "./lambda" timeout 30.0 } </code>
<summary>Sets the runtime for the Lambda function.</summary>
<param name="config">The function configuration.</param>
<param name="runtime">The Lambda runtime.</param>
<code lang="fsharp"> lambda "MyFunction" { runtime Runtime.NODEJS_18_X } </code>
type Runtime = inherit DeputyBase new: name: string * ?family: Nullable<RuntimeFamily> * ?props: ILambdaRuntimeProps -> unit member RuntimeEquals: other: Runtime -> bool member ToString: unit -> string member BundlingImage: DockerImage member Family: Nullable<RuntimeFamily> member IsVariable: bool member Name: string member SupportsCodeGuruProfiling: bool member SupportsInlineCode: bool ...
--------------------
Runtime(name: string, ?family: System.Nullable<RuntimeFamily>, ?props: ILambdaRuntimeProps) : Runtime
<summary>Sets the handler for the Lambda function.</summary>
<param name="config">The function configuration.</param>
<param name="handler">The handler name (e.g., "index.handler").</param>
<code lang="fsharp"> lambda "MyFunction" { handler "index.handler" } </code>
<summary>Sets the code source from a Code object.</summary>
<param name="config">The function configuration.</param>
<param name="path">The Code object.</param>
<code lang="fsharp"> lambda "MyFunction" { code (Code.FromBucket myBucket "lambda.zip") } </code>
<summary>Sets environment variables for the Lambda function.</summary>
<param name="config">The function configuration.</param>
<param name="env">List of key-value pairs for environment variables.</param>
<code lang="fsharp"> lambda "MyFunction" { environment [ "KEY1", "value1"; "KEY2", "value2" ] } </code>
FsCDK