moto review
Moto 5.2.3 intercepts AWS SDK requests and answers them with local Python implementations, letting tests create buckets, queues, tables, secrets, and many other AWS-shaped resources without changing a cloud account. `mock_aws` runs inside the test process; server mode exposes the fake endpoints to another process or language. The current patch adds early support for Clean Rooms, DevOps Agent, and Payment Cryptography, expands several service backends, and removes Panorama after botocore dropped that service. Our lab numbers cover 5.2.2, the prior patch.
Moto 5.2.2 took 1.4 seconds but left 20 packages and 77 MB in our sandbox, so Moto 5.2.3 is worthwhile for a suite with several boto3 flows, not for one trivial client call. Keep a smaller AWS-backed test set for behavior the emulator does not claim to reproduce.
We installed it
| Install | ✓ · 1.4s | 20 packages on disk · 77 MB |
| Import | ✓ | import moto in 1.20s · pure Python · py.typed · requires Python >=3.10 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does moto install cleanly?
Yes. In a fresh container with an empty cache, pip install moto finished in 1 seconds, leaving 20 packages and 77 MB on disk. pip-audit reported no known vulnerabilities.
What does moto need to run?
Python >=3.10, and nothing compiled: it is pure Python. In our run import moto succeeded in 1.20s, and the package ships py.typed for type checkers.
moto or localstack: which should you use?
localstack: Use its container and CLI when several processes need to share a wider HTTP-level AWS emulator. Moto 5.2.2 took 1.4 seconds but left 20 packages and 77 MB in our sandbox, so Moto 5.2.3 is worthwhile for a suite with several boto3 flows, not for one trivial client call.
When should you not use moto?
Do not use Moto to prove IAM enforcement, quotas, regional differences, eventual behavior, or exact AWS error payloads. Those are local implementations, not the real control plane.
Use it if
- Python tests already use boto3 and need isolated AWS-like state for fast local runs.
- A fixture must create S3, DynamoDB, SQS, Secrets Manager, or similar resources before application code executes.
- A separate process or non-Python SDK can be pointed at Moto's standalone HTTP server.
- The project will still run targeted tests against AWS for permissions, quotas, timing, and unsupported methods.
- Do not use Moto to prove IAM enforcement, quotas, regional differences, eventual behavior, or exact AWS error payloads. Those are local implementations, not the real control plane.
- Check the implemented-services table before installing. A named backend may support only part of a boto3 service, method, or parameter set.
- A narrow unit test may not justify the 77 MB and 20 packages recorded in our 5.2.2 environment; a small hand-written fake can be easier to audit.
- Choose LocalStack when several applications need shared emulator state, container networking, and HTTP process isolation instead of an in-process patch.
- Avoid clients created before `mock_aws` starts. Cached endpoints or custom transports can send requests outside the fake boundary.
Setup reality
We installed Moto 5.2.2, one patch behind today's 5.2.3 release, in a fresh Python 3.12 Bookworm container. The install took 1.4 seconds, left 20 packages, and used 77 MB. pip-audit reported 0 known vulnerabilities. Metadata for that measured package lists 92 direct dependencies, Python 3.10+, pure-Python code, py.typed, and Apache-2.0. import moto worked in 1.20 seconds.
Choose service extras instead of moto[all] when the suite touches only a few AWS products. Some emulations pull additional libraries or need Docker-backed execution. Set fake access keys, a fake session token, and a region before boto3 starts, which prevents credential lookup from reaching instance metadata or a developer profile. Enter mock_aws before application imports create clients. A previously constructed client may keep the transport that Moto was meant to replace.
Moto 5.2.3 begins each mock with an empty account. Tests must create every bucket, table, queue, or secret they expect. Decorators, context managers, and fixtures reset state on teardown. For cross-process tests, start moto_server, point every SDK at the same endpoint, and own its port and lifecycle. Any client using that server shares its state, so parallel jobs need isolation or unique resource names.
The service matrix is the contract to inspect, not the service name alone. Version 5.2.3 introduces several backends with partial operation coverage and removes Panorama to follow botocore. Local success proves that your code formed a request Moto accepts and handled the resulting state. Keep AWS-backed checks for IAM policy, presigned URLs, notification delivery, generated identifiers, limits, and operations where real timing changes the result.
Patterns
Create an S3 object under the mock mock-s3
import boto3
from moto import mock_aws
@mock_aws
def test_report_upload():
s3 = boto3.client('s3', region_name='us-east-1')
s3.create_bucket(Bucket='reports-test')
s3.put_object(Bucket='reports-test', Key='daily.csv', Body=b'id,total\n1,12')
body = s3.get_object(Bucket='reports-test', Key='daily.csv')['Body'].read()
assert body.startswith(b'id,total')Construct the client and bucket after the mock begins. AWS bucket-creation rules still depend on the selected region.
Provide fake AWS state from pytest pytest-fixture
import boto3
import pytest
from moto import mock_aws
@pytest.fixture
def aws_services(monkeypatch):
monkeypatch.setenv('AWS_ACCESS_KEY_ID', 'testing')
monkeypatch.setenv('AWS_SECRET_ACCESS_KEY', 'testing')
monkeypatch.setenv('AWS_DEFAULT_REGION', 'us-east-1')
with mock_aws():
yield {'s3': boto3.client('s3'), 'sqs': boto3.client('sqs')}Fake credentials stop boto3 from reading a developer profile or querying instance metadata during the test.
Exercise a DynamoDB round trip mock-dynamodb
import boto3
from moto import mock_aws
@mock_aws
def test_customer_lookup():
db = boto3.resource('dynamodb', region_name='us-east-1')
table = db.create_table(
TableName='customers',
KeySchema=[{'AttributeName': 'id', 'KeyType': 'HASH'}],
AttributeDefinitions=[{'AttributeName': 'id', 'AttributeType': 'S'}],
BillingMode='PAY_PER_REQUEST',
)
table.put_item(Item={'id': 'c-42', 'name': 'Ada'})
assert table.get_item(Key={'id': 'c-42'})['Item']['name'] == 'Ada'This verifies request shape and local state changes. It says nothing about provisioned capacity, latency, or every expression edge case.
Put one message through SQS mock-sqs
import boto3
from moto import mock_aws
@mock_aws
def test_job_queue():
sqs = boto3.client('sqs', region_name='us-east-1')
url = sqs.create_queue(QueueName='jobs')['QueueUrl']
sqs.send_message(QueueUrl=url, MessageBody='job-42')
messages = sqs.receive_message(QueueUrl=url, MaxNumberOfMessages=1)['Messages']
assert messages[0]['Body'] == 'job-42'In-memory receipt does not reproduce production timing, duplicate delivery, visibility races, or long polling.
Read a Secrets Manager value mock-secrets-manager
import boto3
from moto import mock_aws
@mock_aws
def test_secret_loader():
secrets = boto3.client('secretsmanager', region_name='us-east-1')
secrets.create_secret(Name='app/database', SecretString='postgres://test')
value = secrets.get_secret_value(SecretId='app/database')['SecretString']
assert value == 'postgres://test'Moto keeps this value in test state. KMS permissions and automatic rotation are outside what this assertion proves.
Store a Parameter Store flag mock-parameter-store
import boto3
from moto import mock_aws
@mock_aws
def test_feature_flag():
ssm = boto3.client('ssm', region_name='us-east-1')
ssm.put_parameter(Name='/app/new-checkout', Value='enabled', Type='String')
parameter = ssm.get_parameter(Name='/app/new-checkout')['Parameter']
assert parameter['Value'] == 'enabled'A fake `SecureString` cannot demonstrate the encryption key or IAM policy that protects the value in AWS.
Publish an SNS notification mock-sns
import boto3
from moto import mock_aws
@mock_aws
def test_alert_publish():
sns = boto3.client('sns', region_name='us-east-1')
arn = sns.create_topic(Name='alerts')['TopicArn']
response = sns.publish(TopicArn=arn, Message='disk-low')
assert response['MessageId']A message ID from the fake does not verify subscriber delivery, retries, filters, or permissions.
Confine interception to one block use-context-manager
import boto3
from moto import mock_aws
with mock_aws():
sts = boto3.client('sts', region_name='us-east-1')
identity = sts.get_caller_identity()
assert identity['Account']
Clients should be created inside the context. After exit, that mock no longer protects later SDK calls.
Start and stop Moto by hand start-manually
import boto3
from moto import mock_aws
mock = mock_aws()
mock.start()
try:
s3 = boto3.client('s3', region_name='us-east-1')
s3.create_bucket(Bucket='manual-test')
finally:
mock.stop()Always stop in `finally`; a leaked patch can change unrelated tests that run afterward.
Expose Moto over loopback run-server-mode
# Start in a dedicated test process
moto_server -H 127.0.0.1 -p 5000
# Point an SDK client at it
aws --endpoint-url http://127.0.0.1:5000 s3api list-bucketsBind local server mode to loopback and terminate it during teardown. All clients hitting the endpoint share one backend.
Point boto3 at the test server point-boto3-to-server
import boto3
s3 = boto3.client(
's3',
endpoint_url='http://127.0.0.1:5000',
region_name='us-east-1',
aws_access_key_id='testing',
aws_secret_access_key='testing',
)
print(s3.list_buckets()['Buckets'])Server mode needs an explicit endpoint. Keep the production endpoint variable out of this test process.
Assert a missing-object response verify-error-path
import boto3
from botocore.exceptions import ClientError
from moto import mock_aws
@mock_aws
def test_missing_object():
s3 = boto3.client('s3', region_name='us-east-1')
s3.create_bucket(Bucket='errors-test')
try:
s3.get_object(Bucket='errors-test', Key='missing.txt')
except ClientError as exc:
assert exc.response['Error']['Code'] == 'NoSuchKey'
else:
raise AssertionError('expected NoSuchKey')Confirm exact error parity for this Moto method before depending on every field of the emulated response.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| localstack | PyPI | Use its container and CLI when several processes need to share a wider HTTP-level AWS emulator. |
| boto3 | PyPI | Use boto3 against a dedicated AWS test account when IAM and exact service behavior are the test subject. |
| responses | PyPI | Use it for a small HTTP boundary where matching a few requests is clearer than emulating an AWS service. |
More testing guides
pytest · chai · vitest · jsdom · playwright · coverage · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

