Quick Start

Install, create a session in beforeAll, and write a test. Local services start as a subprocess — your application's AWS SDK clients redirect to them automatically.

1

Install

npm install local-web-services-javascript-sdk # also requires local-web-services (Python) for ldk dev pip install local-web-services
2

Configure Jest

// jest.config.js module.exports = { testEnvironment: 'node', testTimeout: 60000, // allow time for ldk dev to start };
3

Write your test

// orders.test.js const { LwsSession } = require('local-web-services-javascript-sdk'); let session; beforeAll(async () => { session = await LwsSession.create({ tables: [{ name: 'Orders', partitionKey: 'id' }], queues: ['OrderQueue'], }); }); afterAll(async () => { await session.close(); }); beforeEach(async () => { await session.reset(); // wipe state between tests }); test('creates an order', async () => { // Call your real application code — it uses standard AWS SDK clients const { createOrder } = require('./orders'); await createOrder({ id: '42', status: 'pending' }); // Assert using the DynamoDB helper const table = session.dynamodb('Orders'); const item = await table.assertItemExists({ id: { S: '42' } }); expect(item.status.S).toBe('pending'); }); test('order sends a queue message', async () => { const { createOrder } = require('./orders'); await createOrder({ id: '99', status: 'pending' }); const queue = session.sqs('OrderQueue'); await queue.assertMessageCount(1); });
4

Run your tests

npx jest

The session starts ldk dev once in beforeAll, then reset() wipes state before each test.

Drop-in Replacement — Zero Code Changes

When a session starts, AWS_ENDPOINT_URL_* environment variables are set for every supported service. Any new DynamoDBClient({}) your application creates will automatically use local services — no client reconfiguration, no dependency injection.

Your application code

// orders.js — unchanged const { DynamoDBClient, PutItemCommand } = require('@aws-sdk/client-dynamodb'); const { SQSClient, SendMessageCommand } = require('@aws-sdk/client-sqs'); // Standard clients — no endpoint config const dynamo = new DynamoDBClient({}); const sqs = new SQSClient({}); async function createOrder(order) { await dynamo.send(new PutItemCommand({ TableName: 'Orders', Item: { id: { S: order.id }, status: { S: order.status }, }, })); await sqs.send(new SendMessageCommand({ QueueUrl: process.env.ORDER_QUEUE_URL, MessageBody: JSON.stringify(order), })); } module.exports = { createOrder };

Your test

// orders.test.js const { LwsSession } = require('local-web-services-javascript-sdk'); let session; beforeAll(async () => { session = await LwsSession.create({ tables: [{ name: 'Orders', partitionKey: 'id' }], queues: ['OrderQueue'], }); // AWS_ENDPOINT_URL_DYNAMODB and AWS_ENDPOINT_URL_SQS // are now set — the clients in orders.js pick them // up automatically on their next call. }); test('creates an order', async () => { const { createOrder } = require('./orders'); await createOrder({ id: '42', status: 'pending' }); await session.dynamodb('Orders') .assertItemExists({ id: { S: '42' } }); await session.sqs('OrderQueue') .assertMessageCount(1); });
🌍

Env Var Injection

Sets AWS_ENDPOINT_URL_DYNAMODB, AWS_ENDPOINT_URL_SQS, AWS_ENDPOINT_URL_S3, and more. Restored to original values when session.close() is called.

No Refactoring

Application code is identical to production. new DynamoDBClient({}) without any endpoint override just works — the AWS SDK v3 reads the env var automatically.

🔓

No Credentials

Local services accept any credentials. No AWS account, no IAM setup, no ~/.aws/credentials needed in your test environment.

LwsSession

All factory methods are async — they start ldk dev and wait until services are ready before resolving.

Factory methods

const { LwsSession } = require('local-web-services-javascript-sdk'); // Explicit resource declaration const session = await LwsSession.create({ tables: [ { name: 'Orders', partitionKey: 'id' }, { name: 'Products', partitionKey: 'sku', sortKey: 'version' }, ], queues: ['OrderQueue', 'DeadLetterQueue'], buckets: ['ReceiptsBucket'], }); // Auto-discover from CDK cloud assembly (cdk.out/) const session = await LwsSession.fromCdk('../my-cdk-project'); // Auto-discover from Terraform .tf files const session = await LwsSession.fromHcl('../my-terraform-project'); // Always close after your tests await session.close();

Client factory and utilities

const dynamo = session.client('dynamodb'); const sqs = session.client('sqs'); const s3 = session.client('s3'); const sns = session.client('sns'); const ssm = session.client('ssm'); const secrets = session.client('secretsmanager'); const sfn = session.client('stepfunctions'); const queueUrl = session.queueUrl('OrderQueue'); // local SQS URL const port = session.portFor('dynamodb'); // port number await session.reset(); // clear all tables, queues, buckets

Resource Helpers

Async helpers with built-in assertion methods. Skip the boilerplate.

DynamoDB

const table = session.dynamodb('Orders'); await table.put({ id: { S: '1' }, status: { S: 'pending' } }); const item = await table.get({ id: { S: '1' } }); await table.delete({ id: { S: '1' } }); const items = await table.scan(); await table.assertItemExists({ id: { S: '1' } }); // returns item await table.assertItemCount(5);

SQS

const queue = session.sqs('OrderQueue'); await queue.send({ orderId: '42', total: 99.99 }); // object → JSON await queue.send('plain text message'); const messages = await queue.receive({ maxMessages: 10 }); await queue.purge(); await queue.assertMessageCount(3); console.log(queue.url); // http://localhost:.../OrderQueue

S3

const bucket = session.s3('ReceiptsBucket'); await bucket.put('receipts/001.pdf', Buffer.from('PDF bytes')); await bucket.put('config.json', JSON.stringify({ key: 'value' })); const data = await bucket.get('receipts/001.pdf'); // Buffer const text = await bucket.getText('config.json'); // string const keys = await bucket.listKeys({ prefix: 'receipts/' }); await bucket.delete('config.json'); await bucket.assertObjectExists('receipts/001.pdf'); await bucket.assertObjectCount({ expected: 5, prefix: 'receipts/' });

Operation Faking

Override any AWS operation response with a fluent builder. No changes to application code needed.

// Inject throttling await session.fake('dynamodb').operation('PutItem').respond({ status: 400, body: { __type: 'ProvisionedThroughputExceededException' }, }); // Add artificial latency await session.fake('sqs').operation('SendMessage').respond({ status: 200, delayMs: 500, }); // Clear all fakes for a service await session.fake('dynamodb').clear();

Chaos Engineering

Inject failure conditions programmatically to verify your application handles them gracefully.

await session.chaos('dynamodb').errorRate(0.3).latency({ minMs: 50, maxMs: 200 }).apply(); await session.chaos('sqs').connectionResetRate(0.1).timeoutRate(0.05).apply(); await session.chaos('dynamodb').clear();

IAM Authorization

Switch IAM modes and define named identities with inline policies to test authorization behaviour.

await session.iam.mode('enforce').apply(); await session.iam.mode('audit').apply(); await session.iam.mode('disabled').apply(); // default await session.iam.defaultIdentity('service-account').apply(); await session.iam .identity('readonly') .allow(['dynamodb:GetItem', 'dynamodb:Scan']) .apply();