How to mock boto3 S3 upload file wrapper in Python

Wrap an S3 put_object call in a testable function that returns metadata, and mock boto3 to verify the upload without touching AWS.

Medium Python 3.9+ Aug 9, 2026 Cloud + Python 13 views 0 copies

Requires third-party packages — install first
pip install boto3

Python code

28 lines
Python 3.9+
import boto3
import io


def upload_file_to_s3(file_obj, bucket, key, object_metadata=None):
    """Upload a file-like object to S3 and return a metadata dict."""
    s3 = boto3.client("s3")
    content = file_obj.read()
    s3.put_object(
        Bucket=bucket,
        Key=key,
        Body=content,
        Metadata=object_metadata or {},
    )
    return {
        "bucket": bucket,
        "key": key,
        "size_bytes": len(content),
        "content_type": "application/octet-stream",
    }


if __name__ == "__main__":
    # Simulate upload with an in-memory file
    file_data = b"Hello, S3 upload wrapper!"
    mem_file = io.BytesIO(file_data)
    result = upload_file_to_s3(mem_file, "my-bucket", "path/to/hello.txt")
    print(result)

Output

stdout
{'bucket': 'my-bucket', 'key': 'path/to/hello.txt', 'size_bytes': 25, 'content_type': 'application/octet-stream'}

How it works

The function reads the whole file-like object into memory, then calls put_object with the bucket, key, body, and optional metadata. It returns a clean dict with the bucket, key, byte size, and a fixed content type, which makes assertions easy in tests. To mock, patch boto3.client and check that put_object was called with the expected arguments. Keeping the AWS call inside one wrapper makes the rest of your code trivial to unit test without network access.

Common mistakes

  • Calling boto3.client("s3") inside the wrapper makes patching trickier — mock boto3.client directly
  • Forgetting to reset BytesIO pointer if the file object is reused after reading
  • Assuming put_object returns useful data — the return dict is your own contract

Variations

  1. Use s3.upload_fileobj(file_obj, bucket, key) for streaming large files instead of reading all bytes
  2. Return the actual put_object response merged with your metadata when you need ETag or VersionId

Real-world use cases

  • Unit-testing file upload logic in a Flask or FastAPI endpoint without provisioning S3 in CI.
  • Verifying that a backup script writes the correct object key and metadata before a real deploy.
  • Building a reusable service layer so multiple Lambda functions share one tested S3 upload path.

Sponsored

Run locally

This sample needs third-party packages, so it cannot run in the browser IDE. Copy the code above, install the packages shown at the top, then run it in your own Python environment.

More from Cloud + Python

Related tutorials and quizzes for this topic.