Imagine you've asked an AI to write you a function that processes photos when someone uploads them to your app. The code looks right. But how do you actually test it?
Ask AI to build a Lambda
Wait 2+ minutes each time
Real AWS calls = real costs
Repeat from step 1
Just to fix one line
An AWS Lambda is a small piece of code that lives in the cloud and runs in response to events — like "a file was uploaded" or "someone hit my API." You write the function, AWS handles all the servers. lambdaforge lets you test that function on your laptop, for free, in seconds.
lambdaforge creates a realistic event JSON file that looks exactly like what AWS would send.
Your handler function runs right on your machine — no cloud needed.
Calls to S3, DynamoDB, SQS etc. are intercepted by moto — so nothing real happens and nothing costs money.
import boto3 import logging logger = logging.getLogger() logger.setLevel(logging.INFO) def handler(event, context): for record in event["Records"]: key = record["s3"]["object"]["key"] logger.info(f"Got: {key}") return {"statusCode": 200}
lambdaforge is made of 5 Python files, each with a clear job. Understanding who does what helps you tell AI exactly where to make changes.
The CLI entry point. Reads your flags, validates inputs, and orchestrates everything else. Think of it as the stage manager.
Five files — one per trigger type — each containing a generate() function that returns a realistic fake event dictionary.
The engine. Loads your handler module at runtime, creates a fake AWS context, runs your function, and captures all output.
Wraps moto's mock_aws() context manager. Intercepts every boto3 call so they never reach the real cloud.
Formats and prints results using Rich — the colored header, captured logs, JSON return value, and timing. This is the last thing that runs.
Watch the components coordinate in real time:
When you ask AI to "add error handling to my Lambda," you now know to say: "In invoker.py, make invoke_handler() catch exceptions and return a structured error dict instead of re-raising." Precise instructions → better results.
Every Lambda function is activated by a trigger — something that happened in the world. Before you can test your function, you need a realistic representation of that trigger. That's what generate creates.
File uploaded, deleted, or modified in an S3 bucket.
Message arrived in an SQS queue. Good for background job workers.
Notification published to an SNS topic. Fan-out broadcasting.
HTTP request hit your API Gateway endpoint. Your REST API backend.
A scheduled or custom event from EventBridge. Scheduled tasks and automation.
{
"Records": [
{
"eventName": "ObjectCreated:Put",
"awsRegion": "us-east-1",
"s3": {
"bucket": {
"name": "<your-bucket-name>"
},
"object": {
"key": "<your-object-key>",
"size": 1024
}
}
}
]
}
Generated events use placeholder values like <your-bucket-name>. Replace them with real values that match what your code expects — the bucket name, file key, or request body your handler will actually process.
When you run invoke, lambdaforge doesn't just call your function — it creates an entire mini-AWS environment around it in milliseconds.
CLI checks the handler path format (file.py::func_name), that the file exists, that event.json is valid JSON, and that any requested mock services are in the supported list.
Using Python's importlib, lambdaforge reads your Python file off disk and executes it to find the function. Like hiring a freelancer: you find their portfolio (the file), pick the skill you need (the function name), and hand them the job.
If you passed --mock, a context manager activates moto's mock environment. Every boto3 call your handler makes is intercepted — nothing reaches the real AWS.
Your function is called with handler(event, context). A fake LambdaContext is passed as the second argument. All print() and logging output is captured, along with execution time. Then it all gets pretty-printed.
def invoke_handler(handler, event): context = LambdaContext() captured = io.StringIO() log_handler = logging.StreamHandler(captured) root_logger = logging.getLogger() root_logger.addHandler(log_handler) start = time.perf_counter() try: with redirect_stdout(captured): result = handler(event, context) finally: root_logger.removeHandler(log_handler) duration_ms = (time.perf_counter() - start) * 1000 return result, captured.getvalue(), duration_ms
"lambdaforge-local" — the pretend function name
A unique UUID generated fresh each invocation
128 MB — matches the Lambda default
Always returns 30,000ms — no real timeout locally
Your Lambda code uses boto3 to talk to S3, DynamoDB, SQS. When you're testing, those calls would go to real AWS — costing money and requiring credentials. Moto stops them before they leave your machine.
Every package (boto3 call) leaving your country (your code) passes through customs (moto). The customs officer inspects it, creates a fake response from their own stockroom, and sends it back — the package never actually crossed the border. Your code gets a response and has no idea it never reached real AWS.
from contextlib import contextmanager from moto import mock_aws SUPPORTED_SERVICES = { "dynamodb", "s3", "ssm", "sqs", "sns" } @contextmanager def activated_mocks(services): with mock_aws(): yield
Moto's fake AWS starts completely empty. Your S3 buckets don't exist yet. Your DynamoDB tables don't exist yet. Your handler must create what it needs — or you create it in a setup step.
--mock s3,dynamodb doesn't mean only S3 and DynamoDB are mocked — mock_aws() intercepts every boto3 call. The services list is just for display and validation.
Mocked state is wiped when the "with" block ends. Every invoke run starts fresh — there's no carry-over between invocations. Perfect for test isolation.
Because moto starts empty, real-world handlers that read from S3 need to either: (1) create the bucket and upload the file in a setup script before invoking, or (2) have your handler gracefully handle a missing resource. This is actually a great way to discover edge cases in your code before production.
You've seen every layer. Here's the full workflow you'd use in a real project — and what to do when things go wrong.
Replace <your-bucket-name> with your actual bucket, fill in the file key, adjust the payload.
As shown in examples/s3_to_dynamo/run_and_display.py: import activated_mocks, load_handler, and invoke_handler directly, then query your mocked services and display with Rich tables — all within the same mock context.
The path in --handler doesn't point to a real file. Check your working directory — lambdaforge resolves paths relative to where you run the command.
You need file.py::function_name — two colons, not one. The file path and function name must be separated by ::.
Your handler is trying to read from an S3 bucket that doesn't exist in the mocked environment. Moto starts empty — create the bucket first, either in your handler's setup or in a run script.
Same issue — your handler is querying a DynamoDB table that hasn't been created in the mock. Add a create_table() call in your handler or a setup step before invoking.
You passed a service name in --mock that lambdaforge doesn't recognize. Supported: s3, dynamodb, sqs, sns, ssm.
CLI (lambdaforge invoke) — quick iteration, checking if a handler works, viewing logs. Great for a fast feedback loop.
Python API — when you need to query mocked services after the handler runs, build automated tests, or display results visually. Import activated_mocks, load_handler, and invoke_handler from lambdaforge.
You can read its code, steer AI to modify it precisely, debug failures without guessing, and build Lambda functions with a fast local feedback loop.