Fix: Moto Not Working — Mock Decorator, Real AWS Calls Leaking, and v4 to v5 Migration
Quick Answer
How to fix Moto errors — mock not activating, real AWS credentials used in tests, ImportError mock_s3 removed in v5, fixtures with multiple services, NoCredentialsError despite mock, and standalone server mode.
The Error
You decorate a test with @mock_s3 and Moto says it doesn’t exist:
from moto import mock_s3 # ImportError in moto 5.x
@mock_s3
def test_upload():
...Or @mock_aws activates but the test still hits real AWS:
from moto import mock_aws
import boto3
@mock_aws
def test_s3():
s3 = boto3.client("s3", region_name="us-east-1")
s3.list_buckets()
# Returns your REAL buckets, not mocked onesOr tests pass individually but fail when run together:
$ pytest test_one.py # Passes
$ pytest test_two.py # Passes
$ pytest test_one.py test_two.py # One of them fails — state leakOr you get NoCredentialsError inside a mocked test:
botocore.exceptions.NoCredentialsError: Unable to locate credentialsOr DynamoDB queries silently return empty:
table.put_item(Item={"id": "1", "name": "Alice"})
response = table.get_item(Key={"id": "1"})
print(response.get("Item")) # None — but you just put it!Moto is the canonical AWS mocking library for Python tests — it provides drop-in replacements for boto3 against in-memory implementations of S3, DynamoDB, SQS, Lambda, IAM, and dozens more services. Moto 5.x (March 2024) consolidated all per-service mocks into a single mock_aws decorator, breaking countless tutorials. This guide covers each common failure.
Why This Happens
Moto patches botocore at the HTTP layer — every AWS API call from boto3 gets intercepted and routed to Moto’s in-memory implementation instead of real AWS. The mock only activates within the decorated context (function, fixture, or class); outside it, boto3 makes real API calls.
The v4 → v5 migration replaced per-service decorators (@mock_s3, @mock_dynamodb) with a unified @mock_aws. Code written for v4 fails on import in v5. The new decorator covers all services automatically.
Fix 1: v4 to v5 Migration
# OLD — moto v4 (broken in v5)
from moto import mock_s3, mock_dynamodb, mock_sqs
@mock_s3
@mock_dynamodb
def test_pipeline():
...
# NEW — moto v5
from moto import mock_aws
@mock_aws
def test_pipeline():
...mock_aws covers every AWS service Moto supports. No need to list them individually.
Install the right version:
# Latest moto v5+
pip install moto
# Or with extras for specific services
pip install "moto[s3,dynamodb,sqs]"
pip install "moto[all]" # All services
pip install "moto[server]" # Standalone HTTP server modeCommon Mistake: Following a tutorial that uses @mock_s3 and importing fails with ImportError: cannot import name 'mock_s3'. The fix is always from moto import mock_aws — the per-service decorators are gone. Tutorials from before March 2024 use the old API.
Fix 2: Real AWS Calls Leaking Despite Mock
from moto import mock_aws
import boto3
@mock_aws
def test_s3():
s3 = boto3.client("s3")
s3.list_buckets()
# Hits real AWS if AWS_PROFILE or AWS_ACCESS_KEY_ID is setMoto patches botocore for the duration of the decorator. But boto3.client() reads credentials at creation time — if those credentials are real, the client may bypass the mock in some setups.
Set fake credentials before the test:
import os
import pytest
from moto import mock_aws
import boto3
@pytest.fixture(autouse=True)
def aws_credentials():
"""Mocked AWS Credentials for moto."""
os.environ["AWS_ACCESS_KEY_ID"] = "testing"
os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
os.environ["AWS_SECURITY_TOKEN"] = "testing"
os.environ["AWS_SESSION_TOKEN"] = "testing"
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
@mock_aws
def test_s3():
s3 = boto3.client("s3")
s3.create_bucket(Bucket="my-bucket")
assert "my-bucket" in [b["Name"] for b in s3.list_buckets()["Buckets"]]Pro Tip: Always include an autouse=True fixture that sets fake AWS credentials. It guarantees no test accidentally uses real credentials — even one bare boto3.client() call without @mock_aws would otherwise hit production. Set this in conftest.py so it applies to every test in the project.
Verify mocking with a smoke test:
@mock_aws
def test_smoke():
s3 = boto3.client("s3")
response = s3.list_buckets()
# Fresh moto state always has 0 buckets
assert len(response["Buckets"]) == 0, "Moto isn't active — got real buckets"Fix 3: Decorator vs Context Manager vs Fixture
Decorator (simplest for single-test mocks):
from moto import mock_aws
@mock_aws
def test_s3():
...Context manager (for partial-test mocking):
from moto import mock_aws
def test_partial():
# Real code here (if any)
with mock_aws():
# Mocked AWS calls
s3 = boto3.client("s3")
s3.create_bucket(Bucket="test")
# Mocked context exits — boto3 reverts to real AWSClass decorator (mocks all methods):
@mock_aws
class TestS3:
def test_one(self):
...
def test_two(self):
...pytest fixture (reusable across tests):
import pytest
from moto import mock_aws
import boto3
@pytest.fixture
def s3_client():
with mock_aws():
client = boto3.client("s3", region_name="us-east-1")
client.create_bucket(Bucket="test-bucket")
yield client
# Mock exits here, cleaning up state
def test_upload(s3_client):
s3_client.put_object(Bucket="test-bucket", Key="file.txt", Body=b"hello")
response = s3_client.get_object(Bucket="test-bucket", Key="file.txt")
assert response["Body"].read() == b"hello"For pytest fixture patterns that pair with Moto, see pytest fixture not found.
Fix 4: State Leakage Between Tests
$ pytest test_a.py test_b.py
# test_a passes, test_b fails because S3 has leftover state from test_aBy default, Moto state lives for the entire process — buckets created in one test persist into the next.
Use fresh mock per test:
import pytest
from moto import mock_aws
import boto3
@pytest.fixture
def s3():
with mock_aws():
yield boto3.client("s3", region_name="us-east-1")
# Mock exits — all moto state cleared
def test_one(s3):
s3.create_bucket(Bucket="test")
assert len(s3.list_buckets()["Buckets"]) == 1
def test_two(s3):
# Fresh state — no buckets from test_one
assert len(s3.list_buckets()["Buckets"]) == 0Or reset explicitly between tests:
from moto.core import reset_model_data
@pytest.fixture(autouse=True)
def reset_moto():
yield
reset_model_data() # Wipe all moto state after each testCommon Mistake: Using @mock_aws at the module level — the mock activates once when the module loads and persists across every test in the file. State leaks freely. Always scope the mock per test or per fixture (function-scoped), not module-scoped.
Fix 5: Multi-Region and Multi-Service Setups
from moto import mock_aws
import boto3
@mock_aws
def test_multi_region():
# Different regions are independent in Moto
us_s3 = boto3.client("s3", region_name="us-east-1")
eu_s3 = boto3.client("s3", region_name="eu-west-1")
us_s3.create_bucket(Bucket="us-bucket")
eu_s3.create_bucket(
Bucket="eu-bucket",
CreateBucketConfiguration={"LocationConstraint": "eu-west-1"},
)
assert "us-bucket" in [b["Name"] for b in us_s3.list_buckets()["Buckets"]]
# Note: list_buckets returns global, both regionsMulti-service workflow:
@mock_aws
def test_sqs_to_lambda_to_s3():
sqs = boto3.client("sqs", region_name="us-east-1")
s3 = boto3.client("s3", region_name="us-east-1")
lambda_client = boto3.client("lambda", region_name="us-east-1")
iam = boto3.client("iam", region_name="us-east-1")
# Create role
role = iam.create_role(
RoleName="lambda-exec",
AssumeRolePolicyDocument=json.dumps({...}),
)
# Create queue
queue = sqs.create_queue(QueueName="my-queue")
# Create bucket
s3.create_bucket(Bucket="my-bucket")
# Create lambda (you control function code in tests)
lambda_client.create_function(
FunctionName="my-fn",
Runtime="python3.12",
Role=role["Role"]["Arn"],
Handler="handler.lambda_handler",
Code={"ZipFile": minimal_zip_with_handler()},
)All services share the same Moto state for the duration of the mock — they can reference each other (Lambda triggering on S3 events, etc., though some advanced integrations don’t fully emulate the real AWS behavior).
Fix 6: Standalone Server Mode
For non-Python clients or end-to-end tests where you want a real HTTP endpoint:
pip install "moto[server]"
moto_server -p 5000import boto3
s3 = boto3.client(
"s3",
endpoint_url="http://localhost:5000",
region_name="us-east-1",
aws_access_key_id="testing",
aws_secret_access_key="testing",
)
s3.create_bucket(Bucket="test")Docker:
docker run -p 5000:5000 motoserver/moto:latest# docker-compose.yml for integration tests
services:
moto:
image: motoserver/moto:latest
ports:
- "5000:5000"
app:
build: .
environment:
AWS_ENDPOINT_URL: http://moto:5000
AWS_ACCESS_KEY_ID: testing
AWS_SECRET_ACCESS_KEY: testing
depends_on:
- motoAWS_ENDPOINT_URL (boto3 1.34+) — set once, all clients use it:
export AWS_ENDPOINT_URL=http://localhost:5000
python my_app.py # All boto3 calls go to moto_serverStandalone server mode is essential for testing applications that aren’t pure Python — Go services, Lambda functions running in containers, multi-process workers.
Fix 7: DynamoDB Specific Patterns
DynamoDB is one of the most-used Moto services. Common patterns:
import boto3
from boto3.dynamodb.conditions import Key, Attr
from moto import mock_aws
@mock_aws
def test_dynamodb():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
# Create table
table = dynamodb.create_table(
TableName="Users",
KeySchema=[
{"AttributeName": "user_id", "KeyType": "HASH"},
],
AttributeDefinitions=[
{"AttributeName": "user_id", "AttributeType": "S"},
],
BillingMode="PAY_PER_REQUEST",
)
# Wait for table to become active (Moto creates synchronously, but real DDB doesn't)
table.wait_until_exists()
# Put items
table.put_item(Item={"user_id": "1", "name": "Alice", "age": 30})
table.put_item(Item={"user_id": "2", "name": "Bob", "age": 25})
# Get item
response = table.get_item(Key={"user_id": "1"})
assert response["Item"]["name"] == "Alice"
# Query (requires partition key)
response = table.query(
KeyConditionExpression=Key("user_id").eq("1"),
)
# Scan (full table)
response = table.scan(FilterExpression=Attr("age").gt(20))Common Mistake: Forgetting to call wait_until_exists() after creating a table. In Moto, table creation is synchronous — but real AWS DynamoDB takes time to provision. Your tests pass in Moto and fail in production because production tests start querying before the table is ready. Always include wait_until_exists() so tests catch the issue early.
Global Secondary Index (GSI):
table = dynamodb.create_table(
TableName="Users",
KeySchema=[
{"AttributeName": "user_id", "KeyType": "HASH"},
],
AttributeDefinitions=[
{"AttributeName": "user_id", "AttributeType": "S"},
{"AttributeName": "email", "AttributeType": "S"},
],
GlobalSecondaryIndexes=[
{
"IndexName": "email-index",
"KeySchema": [{"AttributeName": "email", "KeyType": "HASH"}],
"Projection": {"ProjectionType": "ALL"},
},
],
BillingMode="PAY_PER_REQUEST",
)
table.put_item(Item={"user_id": "1", "email": "[email protected]"})
# Query by email via GSI
response = table.query(
IndexName="email-index",
KeyConditionExpression=Key("email").eq("[email protected]"),
)Fix 8: Lambda and Async Patterns
import io
import json
import zipfile
from moto import mock_aws
def create_lambda_zip():
"""Create a minimal zip for Moto Lambda."""
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as zf:
zf.writestr(
"handler.py",
"def lambda_handler(event, context):\n"
" return {'statusCode': 200, 'body': 'hello'}",
)
buf.seek(0)
return buf.read()
@mock_aws
def test_lambda():
iam = boto3.client("iam", region_name="us-east-1")
lambda_client = boto3.client("lambda", region_name="us-east-1")
role = iam.create_role(
RoleName="lambda-role",
AssumeRolePolicyDocument=json.dumps({
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole",
}],
}),
)
lambda_client.create_function(
FunctionName="my-fn",
Runtime="python3.12",
Role=role["Role"]["Arn"],
Handler="handler.lambda_handler",
Code={"ZipFile": create_lambda_zip()},
)
# Invoke
response = lambda_client.invoke(
FunctionName="my-fn",
Payload=json.dumps({"key": "value"}),
)
payload = json.loads(response["Payload"].read())
assert payload["statusCode"] == 200Important caveat — Moto Lambda executes the function code in a separate process by default. For most CI environments this works, but if your Lambda has heavy dependencies, mock them or use MOTO_LAMBDA_STUB_ECR=true to skip image-based functions.
Still Not Working?
Moto Limits and Real AWS Differences
Moto is an emulation — not 100% behavioral parity. Common gaps:
- IAM policy evaluation is approximate, not full SCP/permission boundary logic
- S3 event notifications to Lambda work but with timing differences
- DynamoDB streams work but pagination behavior may differ
- Cognito user pool sign-in flows are simplified
- EventBridge custom buses have limited integration testing
For features where Moto isn’t fully accurate, supplement with integration tests against real AWS in a separate test stage.
Speeding Up Test Suites
# Avoid moto setup overhead for tests that don't touch AWS
@pytest.fixture
def aws():
with mock_aws():
yieldOnly fixtures that explicitly use aws get the mock — non-AWS tests skip Moto entirely.
Combining Moto with localstack
LocalStack is the “heavier” alternative — runs as a Docker container, supports more services and edge cases. For pure unit tests, Moto is faster. For end-to-end tests of complex workflows, LocalStack is more accurate.
A common split: Moto for fast unit tests, LocalStack (or real AWS staging) for integration tests.
Combining with pytest
import pytest
from moto import mock_aws
import boto3
@pytest.fixture(scope="function")
def aws_credentials():
import os
os.environ["AWS_ACCESS_KEY_ID"] = "testing"
os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
@pytest.fixture
def s3(aws_credentials):
with mock_aws():
yield boto3.client("s3")
@pytest.fixture
def dynamodb(aws_credentials):
with mock_aws():
yield boto3.resource("dynamodb")
# Use them independently
def test_s3(s3):
s3.create_bucket(Bucket="test")
def test_dynamodb(dynamodb):
table = dynamodb.create_table(...)For pytest async fixture patterns when testing async AWS workflows, see pytest fixture not found.
Testing with Async boto3 (aioboto3)
Moto’s mock_aws intercepts botocore — aioboto3 uses the same botocore under the hood, so Moto works:
import pytest
import aioboto3
from moto import mock_aws
@pytest.mark.asyncio
async def test_async_s3():
with mock_aws():
session = aioboto3.Session()
async with session.client("s3", region_name="us-east-1") as s3:
await s3.create_bucket(Bucket="test")
response = await s3.list_buckets()
assert len(response["Buckets"]) == 1For asyncio event loop issues that affect aioboto3 + Moto, see Python asyncio not running.
Snapshot Testing with Moto
For complex setups, snapshot the Moto state for test assertions:
import json
from moto import mock_aws
@mock_aws
def test_full_workflow():
setup_resources()
run_workflow()
# Snapshot the resulting state
s3 = boto3.client("s3")
buckets = sorted([b["Name"] for b in s3.list_buckets()["Buckets"]])
assert buckets == ["expected-bucket-1", "expected-bucket-2"]For LocalStack-based alternatives when Moto’s behavior isn’t sufficient, integration tests against real AWS staging are the next step. For broader AWS testing patterns, see AWS Lambda timeout for runtime-side considerations.
Pre-Configured Mock Resources via Fixtures
For test suites that always need certain resources, package the setup into reusable fixtures:
import pytest
import boto3
from moto import mock_aws
@pytest.fixture
def aws_environment():
"""Standard test AWS environment with pre-created resources."""
with mock_aws():
# S3 buckets
s3 = boto3.client("s3", region_name="us-east-1")
s3.create_bucket(Bucket="app-data")
s3.create_bucket(Bucket="app-logs")
# DynamoDB tables
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
users_table = dynamodb.create_table(
TableName="users",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
users_table.wait_until_exists()
# SQS queues
sqs = boto3.client("sqs", region_name="us-east-1")
sqs.create_queue(QueueName="work-queue")
sqs.create_queue(QueueName="dlq")
yield {
"s3": s3,
"dynamodb": dynamodb,
"sqs": sqs,
}
def test_my_app(aws_environment):
s3 = aws_environment["s3"]
# Test against the pre-configured environmentThis reduces per-test boilerplate dramatically — every test starts with a known-good AWS environment.
For pre-commit hooks that validate boto3 + Moto usage patterns, see pre-commit not working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: freezegun Not Working — Datetime Not Frozen, Timezone Issues, and Async Tests
How to fix freezegun errors — freeze_time decorator not affecting datetime.now, timezone-aware datetime mismatch, time.time not frozen, async test time leak, third-party library still using real time, and tick parameter behavior.
Fix: Nox Not Working — Session Errors, Virtualenv Backends, and Reuse Logic
How to fix Nox errors — no noxfile.py found, session not detected, virtualenv backend uv not installed, session.install fails outside virtualenv, parametrize matrix exploding, and reuse_venv confusion.
Fix: Hypothesis Not Working — Strategy Errors, Flaky Tests, and Shrinking Issues
How to fix Hypothesis errors — Unsatisfied assumption, Flaky test detected, HealthCheck data_too_large, strategy composition failing, example database stale, settings profile not found, and stateful testing errors.
Fix: Tox Not Working — Environment Creation, Config Errors, and Multi-Python Testing
How to fix Tox errors — ERROR cannot find Python interpreter, tox.ini config parsing error, allowlist_externals required, recreating environments slow, pyproject.toml integration, and matrix env selection.