How to Write Test Cases for File Upload (With Examples)
How to Write Test Cases for File Upload (With Examples) starts with understanding the core purpose of the upload feature and the risks it introduces. File upload is a common capability in web and mobi
How to Write Test Cases for File Upload (With Examples) starts with understanding the core purpose of the upload feature and the risks it introduces. File upload is a common capability in web and mobile applications, yet it is also one of the most error‑prone areas because it touches the filesystem, security controls, and often integrates with backend services such as virus scanners, image processors, or storage buckets. A well‑designed test suite for upload must verify that the feature works correctly for valid inputs, rejects invalid ones gracefully, and behaves predictably under stress or unusual conditions. This guide walks you through the full lifecycle of creating high‑signal test cases: from dissecting the upload workflow, through building a concrete test matrix, to automating the checks and tying them back to requirements. You will find a ready‑to‑use table of 20+ example test cases, practical code snippets for both manual and automated execution, and a short checklist you can bookmark for future reference.
How to Write Test Cases for File Upload (With Examples): Foundations
Understanding upload workflow
Before writing any test case, map the end‑to‑end flow of a file upload in your system. Typical steps include:
- User selects a file via a UI control (HTML
, native picker, drag‑and‑drop zone, or API endpoint). - Client‑side validation runs (file type, size, name length).
- The file is transferred to the server, often via a multipart/form‑data POST request or a direct PUT to object storage.
- Server‑side validation occurs (MIME type verification, virus scan, quota check).
- The file is stored permanently or temporarily, and a reference (URL, database record) is returned.
- The UI updates to reflect success or shows an error message.
Each of these steps is a potential failure point. For example, client‑side validation can be bypassed, multipart boundaries can be malformed, and storage services can return transient errors. By enumerating the flow, you ensure that test cases cover not only the happy path but also the boundaries where the system interacts with external components.
Risk areas
Identify the risk categories that are most relevant to upload:
- Functional correctness – does the file arrive intact and usable?
- Security – can malicious file types, paths, or payloads lead to execution or data leakage?
- Performance – how does the system behave under large files, many concurrent uploads, or limited bandwidth?
- Compatibility – do different browsers, devices, or assistive technologies interact correctly with the upload widget?
- Data integrity – are metadata such as EXIF, timestamps, or custom headers preserved?
- Compliance – does the upload respect legal holds, retention policies, or encryption requirements?
These risk areas guide the selection of positive, negative, and edge test cases later on.
Test case anatomy
A test case for file upload should contain the following fields:
| Field | Description |
|---|---|
| ID | Unique identifier (e.g., UP‑001) for traceability. |
| Title | Short, readable summary of what is being validated. |
| Preconditions | System state required before execution (e.g., user logged in, storage quota available). |
| Steps | Ordered actions to perform the test (UI interactions, API calls, data setup). |
| Test Data | Specific file attributes (name, size, type, content) used in the steps. |
| Expected Result | Observable outcome (UI message, HTTP status, stored file checksum, log entry). |
| Post‑conditions | Any cleanup needed (delete uploaded file, reset quota). |
| Priority | Risk‑based ranking (P0‑P2) that informs execution order. |
| Tags | Keywords for filtering (security, boundary, automation). |
Having a consistent template makes it easier to review, maintain, and trace test cases to requirements or user stories.
How to Write Test Cases for File Upload (With Examples): Building the Test Matrix
Positive test cases
Positive tests confirm that the upload works as intended for valid inputs. Start with the most common scenarios and then add variations that exercise different code paths.
| ID | Title | Preconditions | Steps | Test Data | Expected Result |
|---|---|---|---|---|---|
| UP‑001 | Upload a valid PDF under size limit | User authenticated, quota > 10 MB | 1. Open upload dialog 2. Select report.pdf (5 MB, PDF) 3. Click Upload | report.pdf, 5 MB, application/pdf | Upload succeeds, server returns 200 OK with file ID, UI shows success toast |
| UP‑002 | Upload an image with EXIF orientation | User authenticated, storage empty | 1. Drag photo.jpg (2 MB, JPEG) onto drop zone 2. Wait for completion | photo.jpg, 2 MB, image/jpeg, contains EXIF Orientation=6 | Image stored, orientation preserved, thumbnail generated correctly |
| UP‑003 | Upload via API with multipart/form‑data | Service token valid, endpoint reachable | 1. POST to /api/upload with form‑field file containing data.txt (1 KB) | data.txt, 1 KB, text/plain | Response 201 Created, JSON includes { "url": "https://storage.example.com/uploads/data.txt" } |
| UP‑004 | Upload a file with Unicode filename | UTF‑8 support enabled | 1. Choose résumé.pdf (3 MB) 2. Submit | résumé.pdf, 3 MB, application/pdf | File stored with original filename, accessible via same Unicode name |
| UP‑005 | Upload after network retry | Unstable network simulator active | 1. Initiate upload of big.zip (50 MB) 2. Simulate 30 % packet loss after 10 MB transferred 3. Wait for retry | big.zip, 50 MB, application/zip | Upload eventually completes, server logs show retry attempts, final file checksum matches source |
These five cases already cover happy‑path validation, different file types, API usage, Unicode handling, and resilience to transient network issues.
Negative test cases
Negative tests verify that the system correctly rejects invalid or dangerous inputs. They should also check that error messages are helpful and do not leak stack traces.
| ID | Title | Preconditions | Steps | Test Data | Expected Result |
|---|---|---|---|---|---|
| UP‑006 | Upload executable (.exe) file | User authenticated | 1. Select malware.exe (2 MB) 2. Attempt upload | malware.exe, 2 MB, application/octet-stream | Upload rejected, server returns 400 Bad Request with message “File type not allowed” |
| UP‑007 | Upload zero‑byte file | User authenticated | 1. Choose empty.txt (0 bytes) 2. Upload | empty.txt, 0 bytes, text/plain | Upload rejected, validation error “File must contain at least one byte” |
| UP‑008 | Exceed maximum file size | Quota allows 100 MB, limit set at 50 MB | 1. Attempt to upload huge.mov (75 MB) | huge.mov, 75 MB, video/quicktime | Upload stopped client‑side, UI shows “File exceeds maximum size of 50 MB” |
| UP‑009 | Upload with missing CSRF token | User logged in, CSRF protection enabled | 1. Craft raw POST to /upload omitting csrf_token field 2. Send file | test.png, 100 KB, image/png | Server returns 403 Forbidden, logs show CSRF validation failure |
| UP‑010 | Upload filename with path traversal | User authenticated | 1. Choose file named ../../etc/passwd (1 KB) 2. Upload | ../../etc/passwd, 1 KB, text/plain | Upload rejected or filename sanitized to etc_passwd, no escape from intended directory |
| UP‑011 | Upload file with double extension | Extension‑based validation only | 1. Choose image.jpg.php (150 KB) 2. Upload | image.jpg.php, 150 KB, application/x-php | Upload rejected or treated as non‑executable based on final extension |
| UP‑012 | Upload with corrupted multipart boundary | Network simulator can inject malformed data | 1. Send POST with deliberately broken boundary ------WebKitFormBoundary missing trailing -- 2. Attach file | dummy.dat, 10 KB, application/octet-stream | Server returns 400 Bad Request, connection closed gracefully |
These cases address file‑type security, size limits, client‑side vs server‑side validation, injection attempts, and malformed protocol data.
Boundary and edge cases
Boundary tests push the limits of accepted values, while edge cases explore uncommon but possible situations that often surface only in production.
| ID | Title | Preconditions | Steps | Test Data | Expected Result |
|---|---|---|---|---|---|
| UP‑013 | Upload file exactly at size limit | Limit = 10 MB | 1. Choose limit.bin (10 MB, all zeroes) 2. Upload | limit.bin, 10 MB, application/octet-stream | Upload succeeds, server accepts, no truncation |
| UP‑014 | Upload file one byte over limit | Same limit | 1. Choose over.bin (10 MB + 1 B) 2. Upload | over.bin, 10 000 001 bytes | Upload rejected, error “File size exceeds limit” |
| UP‑015 | Upload many small files rapidly | Concurrency allowed | 1. Loop 100 times: select tiny_i.txt (1 B) and upload via AJAX without waiting for previous response | Each tiny_i.txt, 1 B, text/plain | All uploads complete, server returns 200 for each, no lost requests |
| UP‑016 | Upload while storage quota is exhausted | Quota = 0 B remaining | 1. Attempt to upload any file (1 KB) | quota_test.txt, 1 KB, text/plain | Upload rejected, UI shows “Insufficient storage” |
| UP‑017 | Upload file with leading/trailing spaces in name | UI trims? | 1. Choose spaced .txt (note spaces) 2. Upload | spaced .txt , 2 KB, text/plain | System either trims spaces and stores spaced .txt or rejects with “Invalid filename” |
| UP‑018 | Upload file with null byte in name | Filename handling | 1. Provide filename image%00.jpg (URL‑encoded null) 2. Upload | image%00.jpg, 5 KB, image/jpeg | Upload rejected or null byte stripped, resulting file named image.jpg |
| UP‑019 | Upload during server maintenance mode | Maintenance flag enabled | 1. Try to upload while endpoint returns 503 | maint.pdf, 3 MB, application/pdf | Client receives 503 Service Unavailable, shows retry suggestion |
| UP‑020 | Upload file that triggers virus scanner timeout | Scanner configured with 30 s timeout | 1. Upload a large inert file designed to cause scanner to hang (e.g., repetitive patterns) | timeout.bin, 200 MB, application/octet-stream | Upload either proceeds after scanner timeout with warning, or is rejected with “Scan timeout” |
These boundary and edge cases expose off‑by‑one errors, concurrency issues, quota handling, filename sanitization, and interactions with auxiliary services such as antivirus scanners.
Data setup and test data management
Effective test data management prevents flaky tests and ensures reproducibility. Consider the following practices:
- Version‑controlled fixtures – store sample files (PDFs, images, executables) in a Git LFS repository so that every test run uses the exact same bytes.
- Dynamic generation – for size‑based tests, create files on the fly using tools like
dd,head, or Python’sos.urandom. Example:dd if=/dev/zero of=10mb.bin bs=1M count=10. - Cleanup scripts – after each test, delete the uploaded object from storage and remove any database records. Use unique identifiers (UUIDs) in filenames to avoid collisions.
- Mock external services – if virus scanning or image processing is slow or costly, replace those microservices with stubs that return predefined results instantly.
- Data dictionaries – maintain a CSV or JSON file that maps each test ID to its file attributes (name, size, type, expected hash). This enables data‑driven test frameworks to iterate over the matrix automatically.
Implementing these practices early saves time when the upload feature evolves, because you can regenerate only the affected fixtures rather than revisiting every test case manually.
How to Write Test Cases for File Upload (With Examples): Automation Strategies
Choosing tools
Automation can target the UI, the API, or both, depending on where you want to gain confidence.
- UI automation – Use Playwright for web uploads or Appium for native mobile pickers. These tools can interact with file‑picker dialogs, drag‑and‑drop zones, and custom upload widgets.
- API automation – Directly call the upload endpoint with libraries such as
requests(Python),axios(Node.js), orRestAssured(Java). This bypasses UI quirks and is faster for regression. - Hybrid approach – Run a thin UI smoke test to verify that the widget wires correctly to the API, then rely on API tests for extensive data‑driven validation.
When selecting a tool, consider the following criteria:
| Criterion | Playwright (Web) | Appium (Mobile) | REST client (e.g., requests) |
|---|---|---|---|
| Interaction fidelity | High – handles native file picker via setInputFiles | High – can send intents to open picker | None – tests the backend directly |
| Execution speed | Moderate (browser launch) | Moderate–slow (device/emulator) | Very fast (HTTP only) |
| Setup complexity | Low – playwright ) | Medium (Android SDK, device farm) | Low (pip install requests) |
| Ability to mock services | Can intercept network with route | Requires proxy or dependency injection | Easy to replace endpoint with mock server |
| Reporting | Built‑in HTML trace, video | Screenshots, logs | JUnit/XML, custom |
Script skeleton examples
Below are minimal, ready‑to‑run snippets that illustrate each approach. Feel free to adapt them to your test framework (pytest, Jest, JUnit, etc.).
#### Playwright (TypeScript) – UI upload
import { test, expect } from '@playwright/test';
test.describe('File upload UI', () => {
test('uploads a PDF and shows success message', async ({ page }) => {
await page.goto('/upload-page');
// Wait for the file input to be attached to the DOM
const fileInput = page.locator('input[type="file"]');
await fileInput.setInputFiles('tests/fixtures/report.pdf');
// Click the upload button (could be a separate element)
await page.click('button#upload-btn');
// Expect a toast or inline success message
await expect(page.locator('.toast-success')).toHaveText(/Upload complete/i);
// Optional: verify network request
await expect(page.request().fetch(() => true)).toContain([
request => request.url().includes('/api/upload') &&
request.method() === 'POST' &&
request.response()?.status() === 200
]);
});
});
#### Appium (Python) – Android native picker
import unittest
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy
import os
class UploadAndroidTest(unittest.TestCase):
def setUp(self):
caps = {
"platformName": "Android",
"deviceName": "Pixel_4_API_33",
"app": os.getenv("APK_PATH"),
"automationName": "UiAutomator2"
}
self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
def test_upload_image(self):
# Open screen that launches picker
self.driver.find_element(MobileBy.ACCESSIBILITY_ID, "btnPickImage").click()
# Use Android intent to set file directly (bypasses picker UI)
self.driver.push_file("/sdcard/Pictures/test.jpg",
base64.b64encode(open("tests/fixtures/test.jpg","rb").read()))
# Now select the pushed file via picker (implementation depends on app)
self.driver.find_element(MobileBy.XPATH,
"//android.widget.CheckedText[@text='test.jpg']").click()
self.driver.find_element(MobileBy.ACCESSIBILITY_ID, "btnUpload").click()
# Verify toast
toast = self.driver.find_element(By.XPATH,
"//android.widget.Toast[contains(@text,'Upload successful')]")
self.assertTrue(toast.is_displayed())
def tearDown(self):
self.driver.quit()
#### Requests (Python) – API‑level upload
import requests
import hashlib
def test_api_upload():
url = "https://api.example.com/upload"
files = {"file": ("data.txt", open("tests/fixtures/data.txt", "rb"), "text/plain")}
headers = {"Authorization": "Bearer " + get_token()}
resp = requests.post(url, files=files, headers=headers)
assert resp.status_code == 201
json_data = resp.json()
assert "url" in json_data
# Optional: download and verify checksum
download = requests.get(json_data["url"])
assert hashlib.sha256(download.content).hexdigest() == \
hashlib.sha256(open("tests/fixtures/data.txt","rb").read()).hexdigest()
These snippets illustrate the core actions: locating the file input, attaching a file, triggering the upload, and asserting on the response or UI feedback.
Handling async uploads
Many modern applications offload the actual storage to a background worker after accepting the upload request. In such cases, the immediate HTTP response may only acknowledge receipt, while the file becomes available later. To test this pattern:
- Poll for completion – after the POST, repeatedly query a status endpoint (e.g.,
/upload/{id}/status) until it returnsfinishedor a timeout occurs. - Webhook verification – if the system notifies via a callback URL, expose a temporary test endpoint (using tools like
webhook.siteor a local ngrok tunnel) and assert that the payload contains the expected file metadata. - Event‑driven assertion – in an automated test, wait for a message on a queue (Kafka, RabbitMQ) that signals successful processing.
Example using polling in Python:
import time
def wait_for_processing(upload_id, timeout=30):
start = time.time()
while time.time() - start < timeout:
status = requests.get(f"https://api.example.com/upload/{upload_id}/status").json()
if status.get("state") == "finished":
return True
time.sleep(2)
raise TimeoutError("Upload did not finish in time")
Verifying server‑side effects
Beyond confirming that the upload endpoint returned success, validate that the file is correctly persisted:
- Checksum comparison – compute SHA‑256 (or MD5) of the source file and compare it to the hash of the downloaded object from storage (S3, Azure Blob, GCS).
- Metadata validation – ensure that
Content-Type,Content-Length, and any custom headers (e.g.,X-Upload-User-ID) match expectations. - Virus scan log – if your pipeline includes ClamAV or similar, query the scan log or a dedicated audit table to confirm the file was scanned and marked clean.
- Thumbnail generation – for images, request the derived thumbnail endpoint and verify dimensions and file type.
- Database record – assert that a row exists in the
uploadstable with the correctuser_id,filename,upload_ts, andstorage_key.
These assertions turn a simple “HTTP 200” check into a high‑signal verification that the entire upload pipeline functions as intended.
Using SUSA for exploratory upload testing
SUSA (the autonomous QA platform) can complement scripted tests by exploring the upload flow without predefined steps. After you upload an APK or point SUSA at your web URL, it will:
- Dynamically discover file‑picker controls, drag zones, and API endpoints.
- Apply its built‑in personas (e.g., “adversarial” tries to upload executable files, “elderly” may use slower interaction patterns, “power user” attempts bulk uploads).
- Detect crashes, ANRs, or validation bypasses that scripted tests might miss because they follow a happy path.
- Auto‑generate regression scripts in Appium (Android) or Playwright (Web) based on the interactions it discovered, giving you a starting point for further refinement.
To leverage SUSA for upload testing, simply run:
susatest-agent upload --url https://myapp.com/upload --apk ./myapp.apk --personas curious adversarial power-user
The agent will explore, produce a report, and output ready‑to‑run test scripts that you can add to your CI pipeline. Because SUSA learns from each run, subsequent executions focus on newly discovered paths, increasing coverage over time without manual test‑case authoring.
How to Write Test Cases for File Upload (With Examples): Prioritization, Traceability, and Maintenance
Risk‑based prioritization
Not all test cases carry the same weight. Use a simple risk matrix that combines impact (how severe a failure would be) and likelihood (how probable the defect is). Assign each test case a priority label:
| Priority | Impact | Likelihood | Typical Examples |
|---|---|---|---|
| P0 | High (security breach, data loss) | Medium‑High | Upload of executable, path traversal, virus‑scan bypass |
| P1 | Medium (functional block, user frustration) | Medium | Size limit enforcement, zero‑byte file rejection, Unicode filename handling |
| P2 | Low (cosmetic, rare edge) | Low | Leading/trailing spaces in filename, network retry logging |
Apply this matrix during test‑case creation: label each row in the matrix with its priority. When time is limited, execute all P0 and P1 cases first, reserving P2 for nightly or weekly runs.
Traceability matrix to requirements
Link each test case to one or more requirements or user stories. This traceability helps impact analysis when a requirement changes and demonstrates coverage to auditors.
| Test ID | Requirement ID | Requirement Description |
|---|---|---|
| UP‑001 | REQ‑UPL‑001 | User shall be able to upload PDF files up to 10 MB |
| UP‑002 | REQ‑UPL‑002 | Uploaded images shall retain EXIF orientation data |
| UP‑003 | REQ‑UPL‑003 | API endpoint /api/upload shall accept multipart/form‑data |
| UP‑004 | REQ‑UPL‑004 | System shall support Unicode characters in filenames |
| UP‑005 | REQ‑UPL‑005 | Upload shall retry automatically on transient network faults |
| UP‑006 | REQ‑SEC‑001 | System shall reject files with executable extensions |
| UP‑007 | REQ‑VAL‑002 | Zero‑length files shall be rejected with a clear message |
| UP‑008 | REQ‑LIM‑001 | Files larger than the configured maximum shall be blocked |
| UP‑009 | REQ‑SEC‑002 | Missing CSRF token shall result in 403 Forbidden |
| UP‑010 | REQ‑SEC‑003 | Filename containing directory traversal sequences shall be neutralized |
| … | … | … |
Maintain this table in a spreadsheet or a test‑management tool (TestRail, Zephyr, etc.). When a requirement is edited, you can quickly locate the affected test IDs and update them accordingly.
Maintaining test cases as the API evolves
File‑upload APIs often change: new parameters are added, storage backends swap, or validation rules tighten. To keep your test suite reliable:
- Parameterize endpoints and credentials – store base URLs, API keys, and storage bucket names in environment variables or a config file (
test-config.yaml). - Use data‑driven tests – feed the matrix of file attributes from an external CSV/JSON; when a new file type is added, simply append a row.
- Adopt contract testing – if you expose a public upload API, generate an OpenAPI/Swagger contract and run tools like
DreddorPactto verify that your implementation adheres to the contract. - Tag obsolete cases – when a validation rule is removed (e.g., you now allow
.exeuploads for a specific admin flow), mark the corresponding test asdisabledor move it to a legacy suite rather than deleting it outright; this preserves historical knowledge. - Leverage SUSA’s cross‑session learning – after each run, SUSA remembers which screens and dead ends it has explored. When you add a new upload step (e.g., a mandatory CAPTCHA before the file selector), SUSA will automatically adapt its exploration and surface new test scenarios that you can then convert into scripted cases.
By treating test cases as living artifacts that evolve alongside the product, you avoid the accumulation of stale or duplicated checks.
Common Pitfalls and Production‑Only Issues
Virus scanning delays
Many production environments integrate an antivirus scanner that may take seconds or even minutes to finish, especially for large archives. If your test assumes an instantaneous response, you will observe false failures. Mitigation strategies:
- Configure a test‑only scanner with a minimal signature set that returns instantly.
- Mock the scan service to return a clean result after a fixed short delay (e.g., 200 ms) using a tool like WireMock.
- Introduce a polling mechanism in your test that checks a “scan status” field rather than relying on the immediate HTTP response.
Storage quota and cleanup
Upload tests can quickly consume disk space if they do not delete artifacts. In shared CI agents, this leads to “no space left on device” errors that are unrelated to the code under test. Ensure each test:
- Generates a unique object key (UUID or timestamp)
- Calls a cleanup API or directly removes the object from storage after verification
- Handles cleanup failures gracefully (log but do not fail the test unless the failure indicates a broader issue)
Filename encoding and Unicode
Browsers may percent‑encode Unicode characters differently, and some backend frameworks decode them incorrectly, leading to mismatched filenames or 404 errors when retrieving the file. Test with the original name. Include test cases that:
- Send the raw Unicode filename in the
Content-Dispositionheader - Verify that the stored filename matches the source after decoding
- Attempt to retrieve the file using both the encoded and decoded forms to confirm consistency
Concurrent uploads and race conditions
When multiple users uploads and race conditions
High concurrency can expose bugs such as:
- Overwriting of files when two uploads generate the same temporary name
- Exhaustion of file descriptors or upload‑slot limits
- Incorrect quota accounting when parallel requests interleave
To detect these, run a stress test that fires off dozens of upload requests simultaneously (using tools like k6, Locust, or a simple for loop with `
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free