How to Test AWS Lambda Locally (Python, Node.js, Docker and SAM): Why the Emulator Gives You 3 GB and 5 Minutes While Lambda Gives You 128 MB and 3 Seconds

Logeshwaran
—

You can test an AWS Lambda function locally in three ways, from fastest to most realistic: call the handler directly from a unit test with a fake context; run it in an AWS Lambda base image with the Runtime Interface Emulator (docker run -p 9000:8080 public.ecr.aws/lambda/python:3.13 app.handler, then curl -XPOST http://localhost:9000/2015-03-31/functions/function/invocations -d '{}'); or use AWS SAM CLI (sam local invoke), which runs the same images for you. The trap is that local is more generous than the cloud. In our tests, the emulator gave the function 3,008 MB of memory and about 300 seconds by default, while a new Lambda function gets 128 MB and 3 seconds. And even when we told it "128 MB", it let the function allocate 300 MB without complaint, and its REPORT line claimed "Max Memory Used: 128 MB" anyway. Code that passes locally can time out or run out of memory in AWS. This guide shows each method with tested commands, the settings that make local behave like production, and every difference we found.

Ethan's order-processing function for Jake's shop worked perfectly on his laptop. In AWS, it timed out on the first busy evening. "You tested it, didn't you?" Jake asked. "I did," Ethan said. "On a machine that gave it five minutes and three gigabytes. Lambda gave it three seconds."

⚡ Quick Answer

• Fastest: call handler(event, FakeContext()) in a unit test. Unit test.

• Realistic: the Lambda base image + Runtime Interface Emulator on port 9000, invoked with curl. Docker / Podman.

• Make it match production: -e AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 -e AWS_LAMBDA_FUNCTION_TIMEOUT=3 -e AWS_REGION=us-east-1. The defaults trap.

• Memory is not enforced locally; cap the container (--memory) instead. Memory.

• SAM CLI: sam local invoke -e event.json and sam local start-api. SAM.

Errors locally · Test events · AWS calls · The bundled SDK · Node.js · Which method · FAQ

New to Lambda? AWS Lambda runs your code in response to events (an HTTP request, a file landing in S3, a message on a queue) without a server you manage; our plain-English Lambda guide explains handlers, events and the context object. "Testing locally" means running that same handler on your own machine, with an event you supply, before you deploy. The examples here use Python 3.13, and the same ideas work for Node.js, Java and the other runtimes.

🧭 NEW HERE? READ THESE FIRST

New to Lambda or containers? Start with these five pages:

💡 Write down your function's real memory and timeout settings before you test anything locally. Every surprise on this page comes from local defaults that differ from them.

The function we tested

All the tests below use one small handler that reports what it sees about its own environment:

import json, os, time

def handler(event, context):
    if event.get('sleep'):
        time.sleep(event['sleep'])
    if event.get('fail'):
        raise ValueError('order total cannot be negative')
    return {
        'statusCode': 200,
        'body': json.dumps({
            'function_name': context.function_name,
            'memory_limit_in_mb': context.memory_limit_in_mb,
            'remaining_ms_at_start': context.get_remaining_time_in_millis(),
            'aws_request_id': context.aws_request_id,
            'region': os.environ.get('AWS_REGION'),
            'has_credentials': bool(os.environ.get('AWS_ACCESS_KEY_ID')),
            'tmp_writable': os.access('/tmp', os.W_OK),
            'task_writable': os.access('/var/task', os.W_OK),
            'event_keys': sorted(event.keys()),
        })
    }

Save it as code/app.py. The handler name Lambda uses is app.handler: the file name without .py, a dot, and the function name.

Method 1: call the handler directly (fastest)

A Lambda handler is an ordinary function. The fastest test calls it with an event dictionary and a small fake context object, with no container and no AWS account:

"""Run: python3 test_app.py -v"""
import json, os, sys, unittest, uuid
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'code'))
import app

class FakeContext:
    function_name = 'orders-api'
    memory_limit_in_mb = '128'
    aws_request_id = str(uuid.uuid4())
    def get_remaining_time_in_millis(self): return 3000

class HandlerTest(unittest.TestCase):
    def test_ok(self):
        r = app.handler({'order': 1}, FakeContext())
        self.assertEqual(r['statusCode'], 200)
        self.assertEqual(json.loads(r['body'])['function_name'], 'orders-api')
    def test_error(self):
        with self.assertRaises(ValueError): app.handler({'fail': True}, FakeContext())

if __name__ == '__main__': unittest.main()

Both tests passed in 0.018 seconds. This is where most of your testing should happen: business logic, input validation, error handling. What it cannot tell you: whether your dependencies install for Lambda's Linux, whether the handler path is right, how the function behaves under Lambda's time and memory limits, or whether it can reach AWS with the permissions its role will have. The next method covers most of those.

Method 2: the Lambda base image and the Runtime Interface Emulator

AWS publishes container images for every Lambda runtime on Amazon ECR Public, and each one includes the Runtime Interface Emulator (RIE), a small web server that pretends to be the Lambda service (it lives at /usr/local/bin/aws-lambda-rie in the image). You mount your code, start the container, and send it events over HTTP. It works with Docker or Podman; we used Podman, and the commands are the same with docker:

  1. Start the function with your code mounted read-only at /var/task, where Lambda puts it:
    docker run --rm -p 9000:8080 -v "$PWD/code:/var/task:ro" \
        public.ecr.aws/lambda/python:3.13 app.handler
  2. Invoke it from another terminal. The path is always the same, whatever your function is called:
    curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{"order": 1}'
  3. Read the logs in the first terminal: you get the same START, END and REPORT lines you see in CloudWatch.

Our first call returned in about one second (a cold start) and the next ones in a few milliseconds:

{"statusCode": 200, "body": "{\"function_name\": \"test_function\", \"memory_limit_in_mb\": \"3008\",
 \"remaining_ms_at_start\": 299046, \"aws_request_id\": \"403fabdb-3ca2-432a-8972-82be26b04a72\",
 \"region\": null, \"has_credentials\": false, \"tmp_writable\": true, \"task_writable\": false, \"event_keys\": [\"order\"]}"}

That one response holds four differences from a real function, and they are the subject of the next sections: the function is called test_function, it has 3,008 MB and almost 300 seconds, there is no Region, and there are no credentials. Two things do match Lambda: /tmp is writable and the code directory is not (because we mounted it read-only, which we recommend). If you need to install dependencies, build an image FROM public.ecr.aws/lambda/python:3.13 with pip install -r requirements.txt --target "${LAMBDA_TASK_ROOT}", which also proves they install on Lambda's Linux. Our containers guide covers images if Docker is new to you.

The defaults trap: 3,008 MB and 300 seconds locally, 128 MB and 3 seconds in AWS

SettingEmulator default (our test)New Lambda function defaultVariable to set locally
Memory3,008 MB128 MBAWS_LAMBDA_FUNCTION_MEMORY_SIZE
Timeoutabout 300 s (299,046 ms left at start)3 sAWS_LAMBDA_FUNCTION_TIMEOUT
Function nametest_functionYour function's nameAWS_LAMBDA_FUNCTION_NAME
RegionNot setThe function's RegionAWS_REGION
CredentialsNoneThe execution role's temporary keysPass keys or a profile (see AWS calls)

Set the production values when you start the container, and the emulator follows them:

docker run --rm -p 9000:8080 -v "$PWD/code:/var/task:ro" \
    -e AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 -e AWS_LAMBDA_FUNCTION_TIMEOUT=3 \
    -e AWS_LAMBDA_FUNCTION_NAME=orders-api -e AWS_REGION=us-east-1 \
    public.ecr.aws/lambda/python:3.13 app.handler

With those settings, the same call reported memory_limit_in_mb: 128 and about 2,883 ms left at the start. Then we sent an event that sleeps for 5 seconds:

Task timed out after 3.00 seconds
REPORT RequestId: 50160793-...  Duration: 3000.00 ms  Billed Duration: 3000 ms  Memory Size: 128 MB  Max Memory Used: 128 MB

That is what a real timeout looks like. The next call after the timeout started with a fresh INIT START, which also matches Lambda: after a timeout, the execution environment is thrown away and the next request pays a cold start. If your code caches connections or files in global variables, a timeout loses them.

Ethan's fix for Jake's function was boring and effective: put the real memory and timeout in a .env file next to the code, load it with --env-file on every local run, and raise the real timeout to 15 seconds after measuring the slow evening.

Memory is not enforced locally

The memory setting changes what the context object reports, but the emulator does not stop a function from using more. We told it the function had 128 MB and then allocated (and wrote to) 300 MB:

{"allocated_mb": 300, "limit_mb": "128"}
REPORT RequestId: 5f40d516-...  Duration: 309.47 ms  Billed Duration: 310 ms  Memory Size: 128 MB  Max Memory Used: 128 MB

It succeeded, and the REPORT line still claimed "Max Memory Used: 128 MB". With no settings at all, it reported "Memory Size: 3008 MB Max Memory Used: 3008 MB". In other words, the local Max Memory Used is just the configured size repeated, not a measurement. In Lambda, the same function would be killed for running out of memory.

To get a real limit locally, cap the container itself: docker run --memory=128m ... (or podman run --memory=128m). The operating system then stops the process when it goes over, which is close to what Lambda does. To measure real memory use, deploy and read Max Memory Used from the CloudWatch REPORT line, where it is measured.

What errors look like locally (and why HTTP 200 is not success)

When the handler raises an exception, the emulator returns the error as JSON, the same shape Lambda uses:

{"errorMessage": "order total cannot be negative", "errorType": "ValueError",
 "requestId": "379aaa9c-781f-4182-867e-7137925b1a21",
 "stackTrace": ["  File \"/var/task/app.py\", line 7, in handler\n    raise ValueError('order total cannot be negative')\n"]}

The HTTP status of that response was 200. Function errors are delivered as a successful HTTP response with an error body, so a test script that only checks the status code will report a broken function as working. Check the body for errorType. The real Lambda Invoke API behaves the same way, with the extra X-Amz-Function-Error header to mark it.

A wrong handler name is different: it is a setup error, and it came back with HTTP 502:

{"errorMessage": "Handler 'handlr' missing on module 'app'", "errorType": "Runtime.HandlerNotFound", "requestId": "", "stackTrace": []}

Seeing Runtime.HandlerNotFound or Runtime.ImportModuleError locally saves you a deploy: the file name, the function name or a missing dependency is wrong. Missing files are their own family; our ENOENT guide covers paths that work locally and fail in Lambda.

Test events: API Gateway, SQS and the rest

Your function only sees the event you send, so local tests are only as good as your events. Save realistic ones as JSON files and pass them with -d @file. Here is an SQS batch with one bad message, sent to a handler that reports partial batch failures:

import json

def handler(event, context):
    failures = []
    for record in event['Records']:
        body = json.loads(record['body'])
        if body.get('qty', 0) < 0:
            failures.append({'itemIdentifier': record['messageId']})   # retry only this message
    return {'batchItemFailures': failures}
{"Records": [
  {"messageId": "m-1", "receiptHandle": "rh-1", "body": "{\"sku\": \"SCR-S26\", \"qty\": 2}", "attributes": {"ApproximateReceiveCount": "1"},
   "messageAttributes": {}, "md5OfBody": "", "eventSource": "aws:sqs", "eventSourceARN": "arn:aws:sqs:us-east-1:111122223333:orders", "awsRegion": "us-east-1"},
  {"messageId": "m-2", "receiptHandle": "rh-2", "body": "{\"sku\": \"BAT-14\", \"qty\": -1}", "attributes": {"ApproximateReceiveCount": "1"},
   "messageAttributes": {}, "md5OfBody": "", "eventSource": "aws:sqs", "eventSourceARN": "arn:aws:sqs:us-east-1:111122223333:orders", "awsRegion": "us-east-1"}
]}
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d @sqs-event.json
{"batchItemFailures": [{"itemIdentifier": "m-2"}]}

Partial batch responses only take effect in AWS if the event source mapping has ReportBatchItemFailures turned on; locally, you are only testing your code's answer. Three ways to get realistic events:

  • Copy a real one: log the incoming event once in a development deployment (print(json.dumps(event))), then save it as a test file, removing anything sensitive.
  • Generate one with SAM: sam local generate-event sqs receive-message, sam local generate-event apigateway http-api-proxy, sam local generate-event s3 put and so on.
  • Use the console's test events as templates: the Lambda console's Test tab offers samples for each service.

An HTTP API event (payload version 2.0) and a REST API event (version 1.0) have different shapes; send the one your API actually uses, or your local test checks a path that never runs in production.

Calling AWS services from a local function

In Lambda, your function gets temporary credentials from its execution role and AWS_REGION is always set. Locally, neither exists. Our probe function tried to create a DynamoDB client and call STS with no settings:

{"boto3": "1.42.97", "region_env": null,
 "client": "NoRegionError: You must specify a region.",
 "sts": "NoCredentialsError: Unable to locate credentials"}

Two ways to fix it, depending on what you want to test:

  • Real AWS, your own permissions: pass a Region and credentials into the container, for example -e AWS_REGION=us-east-1 -e AWS_PROFILE=dev -v "$HOME/.aws:/root/.aws:ro" (the base images run as root), or export short-lived keys from aws configure export-credentials. Remember the function then runs with your permissions, not its role's, so an AccessDenied in AWS can still surprise you; test the role separately with IAM Policy Simulator or a development deployment. Our IAM policy guide explains roles and policies.
  • No AWS at all: point the SDK at local stand-ins with an endpoint override, such as DynamoDB Local (see our DynamoDB Local guide) or a mock library like moto in unit tests. Read endpoints from an environment variable so production code needs no changes.

The SDK inside the image may be older than yours

That probe also showed something worth checking before you rely on a new AWS feature. The Lambda Python 3.13 base image we pulled (built October 13, 2026) ships boto3 1.42.97. That version has no DynamoDB search_vectors (vector search, generally available since August 5) and no Transit Gateway 1:1 NAT calls (October 23). Calling them inside the image fails:

AttributeError: 'DynamoDB' object has no attribute 'search_vectors'
AttributeError: 'EC2' object has no attribute 'get_transit_gateway_attachment_nat_configuration'

On your laptop, with a current boto3, the same code works. This is one of the best reasons to test in the base image: it catches the gap before production does. The fix is to package the SDK version you need with your function, in the deployment package or a layer (pip install --target ./package "boto3>=1.43"), and log boto3.__version__ once at startup. The same applies to the AWS SDK for JavaScript in the Node.js runtimes.

Method 3: AWS SAM CLI

SAM CLI wraps the same images and emulator in commands, and reads your function's settings from a template.yaml, which removes the defaults trap if your template has the real memory and timeout:

sam build
sam local invoke OrdersFunction -e events/sqs.json          # one invocation
sam local start-api                                         # API Gateway routes on http://127.0.0.1:3000
sam local start-lambda                                      # an endpoint your SDK can call with Invoke
sam local generate-event sqs receive-message > events/sqs.json

Use SAM when your project is a SAM or CloudFormation template, or when you want to test API Gateway routes end to end. sam local start-api serves your API on port 3000 and runs the function for each request. It needs Docker (or a Docker-compatible engine such as Podman with its socket enabled) and pulls the runtime images on first use. If you build with the CDK, synthesize first and point SAM at the generated template. For .NET functions, AWS also offers the Lambda Test Tool, which runs handlers in a local web UI.

The same thing for Node.js

For Node.js, use the Node.js base image and an exported handler. With code/index.mjs:

export const handler = async (event, context) => {
  return { statusCode: 200, body: JSON.stringify({ name: context.functionName, memory: context.memoryLimitInMB }) };
};
docker run --rm -p 9000:8080 -v "$PWD/code:/var/task:ro" \
    -e AWS_LAMBDA_FUNCTION_MEMORY_SIZE=128 -e AWS_LAMBDA_FUNCTION_TIMEOUT=3 \
    public.ecr.aws/lambda/nodejs:22 index.handler
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'

Everything above applies: set the memory and timeout, cap the container's memory, and check the bundled SDK version. Pull the image again from time to time (docker pull public.ecr.aws/lambda/nodejs:22): AWS rebuilds the base images for security fixes, and a cached image keeps testing against the old runtime.

Which local testing method should you use?

MethodSpeedCatchesMisses
Call the handler (unit test)MillisecondsLogic, validation, error handlingPackaging, handler path, limits, runtime SDK
Base image + emulatorSecondsPackaging, handler path, timeouts (if set), bundled SDK, read-only code directoryMemory limits (unless you cap the container), IAM role, triggers
SAM CLISecondsAll of the above, settings from your template, API Gateway routesIAM role, real event source behavior (retries, batching), VPC networking
A development deploymentA minuteEverything, including the role, triggers and real memory useNothing, but it costs a deploy

A good workflow uses all of them: unit tests on every save, the base image before each commit, and a development deployment before production. "So the laptop test isn't wrong," Jake said. "It's just generous," Ethan said. "I make it stingy on purpose now."

A checklist that makes local tests honest

  1. Load the function's real memory and timeout into the container (--env-file lambda.env), and cap the container's memory to the same size.
  2. Set AWS_REGION, and decide whether the test uses real AWS (your credentials) or local stand-ins.
  3. Mount the code read-only at /var/task; write only to /tmp.
  4. Use realistic, saved events for every trigger the function has.
  5. Check response bodies for errorType, not just the HTTP status.
  6. Pull the base image again before testing a release, and log the SDK version.
  7. Finish with a development deployment and read Max Memory Used and Duration from the real REPORT line.

Problems people hit testing Lambda locally

Runtime.HandlerNotFound: Handler 'handlr' missing on module 'app'

The handler string does not match the code: check the file name (without .py) and the function name, and that the file is at the root of /var/task.

Runtime.ImportModuleError: Unable to import module

A dependency is missing from the image or package, or it was built for your laptop's operating system instead of Lambda's Linux. Install dependencies inside a Lambda base image build.

It works locally but times out in AWS

The emulator defaulted to about 300 seconds. Set AWS_LAMBDA_FUNCTION_TIMEOUT to the real value locally, and raise the real timeout if the work genuinely needs longer.

It works locally but runs out of memory in AWS

The emulator does not enforce memory. Cap the container with --memory and measure real use from the CloudWatch REPORT line.

NoRegionError: You must specify a region

AWS_REGION is not set locally. Pass -e AWS_REGION=us-east-1 (or your Region) when you start the container.

NoCredentialsError: Unable to locate credentials

The container has no credentials. Mount a profile or pass short-lived keys, or point the SDK at local stand-ins.

AttributeError: object has no attribute (a new AWS API)

The SDK bundled in the runtime image is older than the API. Package a newer SDK with the function.

curl: (7) Failed to connect to localhost port 9000

The container is not running or the port is not published. Start it with -p 9000:8080 and wait a second before the first call.

Testing Lambda locally: frequently asked questions

Can you test a Lambda function locally?

Yes. Call the handler directly in a unit test, run it in an AWS Lambda base image with the Runtime Interface Emulator, or use AWS SAM CLI with sam local invoke.

How do I test an AWS Lambda function locally with Docker?

Run docker run -p 9000:8080 -v "$PWD/code:/var/task:ro" public.ecr.aws/lambda/python:3.13 app.handler, then POST an event to http://localhost:9000/2015-03-31/functions/function/invocations with curl.

What is the Lambda Runtime Interface Emulator?

A small web server included in the AWS Lambda base images that imitates the Lambda service's runtime API, so you can invoke a function in a container with HTTP requests.

Why does my Lambda work locally but time out in AWS?

Local runs default to about 300 seconds and 3,008 MB, while a new function gets 3 seconds and 128 MB. Set the real values locally with AWS_LAMBDA_FUNCTION_TIMEOUT and AWS_LAMBDA_FUNCTION_MEMORY_SIZE.

Does the Lambda emulator enforce the memory limit?

No. In our test, a function set to 128 MB allocated 300 MB without error. Use the container's --memory option to enforce a limit.

How do I test a Python Lambda locally?

For logic, call handler(event, FakeContext()) in a unit test. For packaging and limits, run the public.ecr.aws/lambda/python image with your code mounted at /var/task and invoke it with curl.

How do I pass a test event to a local Lambda?

Save it as a JSON file and send it with curl -d @event.json to the emulator, or use sam local invoke -e event.json. sam local generate-event creates sample events for many services.

How do I test SQS to Lambda locally?

Send an SQS-shaped event with a Records list to your handler, and check its batchItemFailures response. Retries and batching themselves only happen with a real event source mapping in AWS.

How do I test API Gateway and Lambda locally?

Use sam local start-api to serve your template's API routes on port 3000, or send an HTTP API or REST API event shape to the emulator yourself.

Can a local Lambda call real AWS services?

Yes, if you give the container a Region and credentials. It then runs with your permissions, not the function's role, so test the role separately.

Why does my local Lambda say NoRegionError?

The emulator does not set AWS_REGION. Pass it as an environment variable when you start the container.

What does a Lambda error look like when testing locally?

A JSON body with errorMessage, errorType and stackTrace, returned with HTTP 200. Check the body, not only the status code.

Is SAM CLI required to test Lambda locally?

No. The base images and the emulator work with plain docker run. SAM adds convenience: settings from your template, generated events and a local API Gateway.

Why does a custom container image run differently locally than in Lambda?

Local runs have more memory and time, no role, no Region and a writable code directory unless you change that. Set the environment variables, cap memory and mount code read-only to match Lambda.

How do I test a .NET (C#) Lambda locally?

Use the AWS .NET Lambda base image with the emulator the same way, or AWS's Lambda Test Tool, which runs .NET handlers locally with a web UI.

What's the best Lambda development and debug cycle?

Unit tests on the handler for every change, the base image with production settings before each commit, and a development deployment before production, reading the real REPORT lines.

For readers preparing for an AWS certification

We have been covering AWS certifications alongside our fixes, so here is a short extra for readers studying for one. If you only came for the fix, you already have it; feel free to skip this part.

Exams that test this topic: Developer – Associate (DVA-C03 from December 1, 2026) • CloudOps Engineer – Associate (SOA-C03) • Solutions Architect – Associate (SAA-C03).

Here are the exam-style questions this topic tends to raise, with the reasoning you need to answer them:

1. A developer wants to run Lambda functions and their API Gateway routes on a laptop, using the same runtime images Lambda uses. Which tool fits?

AWS SAM CLI: sam local invoke for single invocations and sam local start-api for local API Gateway routes.

2. A function works on a developer's machine but times out in AWS on its first run. Its configuration was never changed after creation. What is the likely cause?

The default Lambda timeout of 3 seconds (and default memory of 128 MB); increase the function's timeout (and memory if needed) to fit the measured work.

3. A Python function calls an AWS API released recently and fails in Lambda with AttributeError, although it works locally. How should the developer fix it?

Package a newer boto3 with the function (in the deployment package or a layer), because the SDK bundled with the runtime can predate the API.

4. A Lambda function processes SQS batches. Only the messages that fail should be retried, not the whole batch. What should be configured?

Enable ReportBatchItemFailures on the event source mapping and return batchItemFailures with the failed message IDs.

5. How can an operations engineer find how much memory a Lambda function actually uses?

Read Max Memory Used from the REPORT lines in CloudWatch Logs (or use Lambda Insights), then right-size the memory setting.

6. An application invokes a Lambda function synchronously and gets HTTP 200, but the function threw an exception. How can the caller detect the error?

Check the FunctionError field (the X-Amz-Function-Error header) and the error payload; function errors are returned with a 200 status.

Exam codes and dates change often. Our AWS certification changes page keeps track of what is current.

πŸ“š ALSO READ

More Lambda guides for the same project:

📌 Bookmark this page. The base images change every few weeks; pull them again before you trust a local result.

The next evening rush went through without a timeout. Ethan's local tests now run with 128 MB and 3 seconds, in a container capped at 128 MB, so the laptop fails first. "That's backwards," Jake said. "That's the point," Ethan said. "I'd rather my laptop say no than your customers." If your results differ from what this page describes, please tell us. Guides like this stay right because readers write in.

📌 If you keep one line from this page

Test Lambda locally in the AWS base image, but set the real memory and timeout and cap the container's memory, because the emulator defaults to 3 GB and 5 minutes and never enforces memory at all.

Port 9000 · /2015-03-31/functions/function/invocations · AWS_LAMBDA_FUNCTION_MEMORY_SIZE · AWS_LAMBDA_FUNCTION_TIMEOUT · AWS_REGION · October 24, 2026.

Revision note. Written October 24, 2026, from tests with the Lambda Python 3.13 base image (built October 13) and its Runtime Interface Emulator. "Practice makes perfect," the saying goes, but only if the practice ground is as hard as the match.

Related