基于AsyncHTTPTestCase测试Tornado的multipart请求
Great question! Testing multipart file uploads in Tornado using AsyncHTTPTestCase is totally doable—you just need to construct the correct multipart/form-data request payload and headers. Let's break this down with concrete examples.
Step 1: Create Your Tornado Upload Handler
First, let's define a simple handler that accepts file uploads (this is the API you're testing):
import tornado.web class UploadHandler(tornado.web.RequestHandler): async def post(self): # Get the uploaded file from the request file_data = self.request.files.get("file") if not file_data: self.set_status(400) self.write({"error": "No file uploaded"}) return file_info = file_data[0] filename = file_info["filename"] content = file_info["body"] # For testing purposes, return metadata about the file self.write({ "filename": filename, "size": len(content), "content_type": file_info["content_type"] })
Step 2: Write the Test Case with AsyncHTTPTestCase
We'll cover two approaches: building the multipart payload manually (no external dependencies) and using a helper library for cleaner code.
Approach 1: Manual Multipart Payload Construction
This method uses only built-in Python modules, so no extra installs are needed:
import tornado.testing import tornado.escape class TestUploadAPI(tornado.testing.AsyncHTTPTestCase): def get_app(self): # Return your Tornado app with the upload handler return tornado.web.Application([ (r"/upload", UploadHandler), ]) def test_upload_file_manual(self): # Define a unique boundary string (separates parts in the multipart form) boundary = b"----TornadoTestBoundary123" # File details filename = b"sample.txt" file_content = b"Hello, this is a test file content!" content_type = b"text/plain" # Build the multipart form parts multipart_parts = [ b"--" + boundary, b'Content-Disposition: form-data; name="file"; filename="' + filename + b'"', b"Content-Type: " + content_type, b"", # Empty line separates headers from file content file_content, b"--" + boundary + b"--", # End of multipart form b"" ] # Join all parts with CRLF (required by multipart standards) request_body = b"\r\n".join(multipart_parts) # Set necessary headers headers = { "Content-Type": f"multipart/form-data; boundary={boundary.decode('utf-8')}", "Content-Length": str(len(request_body)) } # Send the POST request response = self.fetch( "/upload", method="POST", headers=headers, body=request_body ) # Validate the response self.assertEqual(response.code, 200) response_data = tornado.escape.json_decode(response.body) self.assertEqual(response_data["filename"], "sample.txt") self.assertEqual(response_data["size"], len(file_content)) self.assertEqual(response_data["content_type"], "text/plain")
Approach 2: Using a Helper Library (requests-toolbelt)
If you prefer a cleaner approach without manual payload building, use requests-toolbelt's MultipartEncoder to handle formatting:
First, install the library:
pip install requests-toolbelt
Then, update your test method:
from requests_toolbelt.multipart.encoder import MultipartEncoder class TestUploadAPI(tornado.testing.AsyncHTTPTestCase): # Keep the get_app() method from Approach 1 def test_upload_file_with_toolbelt(self): # Create a MultipartEncoder with the file field encoder = MultipartEncoder( fields={ "file": ("sample.txt", b"Hello, test content!", "text/plain") } ) # The encoder handles the Content-Type header with the correct boundary headers = { "Content-Type": encoder.content_type } # Fetch the upload endpoint with the encoded body response = self.fetch( "/upload", method="POST", headers=headers, body=encoder.read() ) # Validate the response self.assertEqual(response.code, 200) response_data = tornado.escape.json_decode(response.body) self.assertEqual(response_data["filename"], "sample.txt") self.assertEqual(response_data["size"], len(b"Hello, test content!"))
Key Notes
- Tornado 4.5+ fully supports multipart/form-data requests, so
self.request.fileswill correctly parse uploaded files into a dictionary of lists. - When building the payload manually, use CRLF (
\r\n) to separate parts—this is required by the multipart standard. - The boundary string must be unique and not appear anywhere in the file content to avoid parsing errors.
内容的提问来源于stack exchange,提问作者Trần Kim Dự

