Skip to content

Switching from Moto

Moto mocks AWS at the Python SDK level using decorators or a standalone HTTP server. CloudMock is a real HTTP server that any language’s AWS SDK can point at — it works with Python, Go, Node, Java, Rust, and more. Migration is straightforward for both pytest and unittest projects.

MotoCloudMock
Works with any languageNo (Python only in-process)Yes — HTTP server for all SDKs
In-process speedYes (Python)Yes (Go, ~20 μs/op)
DevTools UINoYes (localhost:4500)
Chaos engineeringNoBuilt-in, free
State snapshotsNoBuilt-in, free
Traffic replayNoBuilt-in, free
IaC support (Terraform, CDK)NoBuilt-in, free
Protocol-level testingNo (intercepts at SDK)Yes (real HTTP wire protocol)
Service count150+ (mock depth varies)99 (fully emulated)
# Before (Moto)
import boto3
import pytest
from moto import mock_aws
@mock_aws
def test_create_bucket():
s3 = boto3.client("s3", region_name="us-east-1")
s3.create_bucket(Bucket="my-bucket")
response = s3.list_buckets()
assert len(response["Buckets"]) == 1
# After (CloudMock)
import pytest
from cloudmock import CloudMock
@pytest.fixture(scope="session")
def aws():
with CloudMock() as cm:
yield cm
def test_create_bucket(aws):
s3 = aws.boto3_client("s3")
s3.create_bucket(Bucket="my-bucket")
response = s3.list_buckets()
assert len(response["Buckets"]) == 1
# Before (Moto)
from moto import mock_aws
@mock_aws
class TestS3:
def test_put_object(self):
s3 = boto3.client("s3", region_name="us-east-1")
s3.create_bucket(Bucket="my-bucket")
s3.put_object(Bucket="my-bucket", Key="hello.txt", Body=b"world")
# After (CloudMock)
import pytest
from cloudmock import CloudMock
@pytest.fixture(scope="class")
def aws():
with CloudMock() as cm:
yield cm
class TestS3:
def test_put_object(self, aws):
s3 = aws.boto3_client("s3")
s3.create_bucket(Bucket="my-bucket")
s3.put_object(Bucket="my-bucket", Key="hello.txt", Body=b"world")
conftest.py
import pytest
from cloudmock import CloudMock
@pytest.fixture(scope="session")
def cloudmock_instance():
with CloudMock() as cm:
yield cm
@pytest.fixture
def s3(cloudmock_instance):
return cloudmock_instance.boto3_client("s3")
@pytest.fixture
def dynamodb(cloudmock_instance):
return cloudmock_instance.boto3_client("dynamodb")
@pytest.fixture
def sqs(cloudmock_instance):
return cloudmock_instance.boto3_client("sqs")
# Before (Moto)
import unittest
import boto3
from moto import mock_aws
class TestDynamoDB(unittest.TestCase):
@mock_aws
def test_create_table(self):
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="users",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
tables = dynamodb.list_tables()["TableNames"]
self.assertIn("users", tables)
# After (CloudMock)
import unittest
from cloudmock import CloudMock
class TestDynamoDB(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.cm = CloudMock()
cls.cm.__enter__()
@classmethod
def tearDownClass(cls):
cls.cm.__exit__(None, None, None)
def test_create_table(self):
dynamodb = self.cm.boto3_client("dynamodb")
dynamodb.create_table(
TableName="users",
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
tables = dynamodb.list_tables()["TableNames"]
self.assertIn("users", tables)

Moto has a standalone server mode (moto_server) that behaves like CloudMock’s HTTP mode. Switching is a direct replacement:

Terminal window
# Before (Moto server mode)
pip install moto[server]
moto_server -p 5000
# then point boto3 at http://localhost:5000
# After (CloudMock)
npx cloudmock
# or: brew install viridian-inc/tap/cloudmock && cloudmock
# point boto3 at http://localhost:4566

Update your boto3 clients:

# Before (Moto server mode)
import boto3
s3 = boto3.client(
"s3",
endpoint_url="http://localhost:5000",
region_name="us-east-1",
aws_access_key_id="test",
aws_secret_access_key="test",
)
# After (CloudMock HTTP mode)
import boto3
s3 = boto3.client(
"s3",
endpoint_url="http://localhost:4566",
region_name="us-east-1",
aws_access_key_id="test",
aws_secret_access_key="test",
)

Or use the CloudMock SDK to skip manual configuration entirely:

from cloudmock import CloudMock
with CloudMock() as cm:
s3 = cm.boto3_client("s3") # endpoint, credentials, region pre-configured
sqs = cm.boto3_client("sqs")
MotoCloudMockNotes
@mock_aws decoratorCloudMock() context managerWraps entire test or fixture scope
@mock_s3, @mock_dynamodbSame CloudMock() — all services activeNo per-service decorators needed
moto_server -p 5000npx cloudmock (port 4566)Real HTTP server, same pattern
In-process Python mockingHTTP server (Python) or in-process (Go)Python tests use HTTP; Go gets 20 μs/op
Per-decorator isolationFixture scope controls isolationUse scope="function" for per-test
boto3.client(endpoint_url=...)cm.boto3_client("s3")Auto-configured credentials

Moto resets state between each @mock_aws decorated test automatically. CloudMock resets when you restart or re-enter the context manager. For per-test isolation, use a function-scoped fixture:

# Per-test isolation (equivalent to @mock_aws on each test)
@pytest.fixture
def aws():
with CloudMock() as cm:
yield cm
# CloudMock starts fresh for every test function

For faster tests that share state across a suite:

# Shared state (faster — one CloudMock instance for the whole session)
@pytest.fixture(scope="session")
def aws():
with CloudMock() as cm:
yield cm
# Before (Moto — no special setup needed, just pip install)
- run: pip install moto pytest boto3
- run: pytest
# After (CloudMock)
- uses: viridian-inc/cloudmock-action@v1
- run: pip install cloudmock pytest boto3
- run: pytest
FeatureMotoCloudMock
Language supportPython only (in-process)Any language via HTTP
Protocol-level testingNo (SDK intercept)Yes (real HTTP wire protocol)
DevTools UINoYes (topology, traces, chaos)
Chaos engineeringNoBuilt-in, free
State snapshotsNoBuilt-in, free
Traffic replayNoBuilt-in, free
IaC supportNoTerraform, CDK, Pulumi
GitHub ActionNoviridian-inc/cloudmock-action@v1
Multi-language team supportNoYes
Contract testingNoBuilt-in, free
  • HTTP overhead: Moto’s in-process Python mode has zero network overhead; CloudMock’s Python SDK uses HTTP (still fast — ~1ms/op). If you need zero-network speed, use CloudMock’s Go SDK (~20 μs/op).
  • Test isolation model: Moto resets state per decorator; CloudMock resets per context manager scope. Use scope="function" in pytest for equivalent per-test isolation.
  • Service decorator specificity: Moto lets you use @mock_s3 to mock only S3. CloudMock runs all 100 services always — there’s no per-service activation.
  • Lambda execution: Moto can execute simple Lambda handlers in-process. CloudMock stores Lambda configuration but does not execute handlers.
  • License: CloudMock uses BSL-1.1 (free for dev/test, commercial production hosting requires a license). Moto uses Apache 2.0.