How to Write Test Cases for Image Upload (With Examples)
How to Write Test Cases for Image Upload (With Examples)
How to Write Test Cases for Image Upload (With Examples)
Image upload is a common feature in web and mobile applications, yet it hides a surprising amount of complexity. of risk. A single upload endpoint must validate file type, size, dimensions, metadata, and often perform server‑side processing such as resizing, virus scanning, or storage integration. Because the surface area touches the file system, network, and sometimes third‑party services, defects can range from cosmetic UI glitches to security vulnerabilities or data loss. Writing effective test cases for this feature therefore requires a systematic approach that covers happy‑path behavior, invalid inputs, boundary conditions, and edge‑case scenarios that only surface under load or with specific file characteristics.
This guide walks you through a complete process: from dissecting the feature and defining a solid test‑case template, to building a concrete matrix of 20+ example cases, preparing test data, prioritizing effort, linking to requirements, and finally combining manual and automated execution with autonomous exploration to achieve real coverage. Each section includes practical examples, command‑line snippets, and code fragments you can copy into your own test suites. By the end you will have a ready‑to‑use checklist and a clear mental model for tackling image upload testing on any platform.
1. Understanding the Image Upload Feature
Before writing any test case you need a shared mental model of what the upload feature does, where it lives in the architecture, and which requirements drive its behavior.
1.1 Typical flow and components
Most implementations follow a similar sequence:
- Client‑side selection – user picks a file via an
control or a native picker. The client may perform preliminary checks (e.g., show a preview, enforce a maximum size via JavaScript). - Client‑side validation – optional JavaScript validates MIME type, extension, or dimensions before sending data.
- Request formation – the file is packaged in a
multipart/form-databody, often together with metadata such as a caption, album ID, or user token. - Network transmission – the HTTP POST (or PUT) is sent to an endpoint like
/api/v1/upload. - Server‑side receipt – the backend parses the multipart stream, saves the raw bytes to a temporary location, and begins validation.
- Validation pipeline – checks include file extension whitelist, MIME type sniffing, size limits, image integrity (e.g., using ImageMagick or libpng), dimension constraints, and sometimes virus scanning.
- Processing – if validation passes, the image may be resized, converted to a standard format, stripped of EXIF data, or have watermarks applied.
- Storage – the final image is written to object storage (S3, GCS), a database blob, or a file system, and a URL or identifier is returned to the client.
- Response – the client receives a JSON payload with success status, the new URL, and any relevant metadata.
Understanding each step helps you decide where to inject faults. For example, a test that sends a corrupted JPEG will fail at step 6, whereas a test that omits the Content-Type header may be rejected earlier by the server’s multipart parser.
1.2 Key requirements to capture
Collect the functional and non‑functional requirements that govern the upload. Typical items include:
| Requirement ID | Description |
|---|---|
| REQ-UPL-001 | Accept JPEG, PNG, GIF, WebP files only. |
| REQ-UPL-002 | Maximum file size 10 MB. |
| REQ-UPL-003 | Minimum dimensions 100 × 100 px; maximum 5000 × 5000 px. |
| REQ-UPL-004 | Reject files with mismatched extension and actual content (e.g., .jpg that is a ZIP). |
| REQ-UPL-005 | Strip EXIF GPS coordinates for privacy. |
| REQ-UPL-006 | Return a 415 Unsupported Media Type for disallowed MIME types. |
| REQ-UPL-007 | Return a 413 Payload Too Large for oversized files. |
| REQ-UPL-008 | Store the processed image in S3 bucket user‑uploads with public‑read ACL. |
| REQ-UPL-009 | Log upload attempts (user ID, filename, size, outcome) to audit table. |
| REQ-UPL-010 | Ensure no server‑side code execution via uploaded file (secure against shell‑shock style). |
These requirements become the traceability anchors for each test case you write.
2. Anatomy of a Good Test Case
A test case is more than a list of steps; it is a contract that specifies the preconditions, the actions, and the observable outcome. A well‑structured case reduces ambiguity, makes automation easier, and simplifies traceability.
2.1 Test case ID and naming convention
Use a hierarchical identifier that reflects intent
Adopt a naming scheme that encodes the feature area, the test type, and a sequential number. For image upload you might use IU- (Image Upload) followed by a three‑digit code and a short descriptor, e.g., IU-001-ValidJpegUnderLimit. This makes it easy to be consistent across test management tools (Zephyr, TestRail, Xray) and automation frameworks (pytest, JUnit).
2.2 Preconditions and test data
List everything that must be true before the first step. This often includes:
- A valid user session or authentication token.
- The upload endpoint reachable (no network partitions).
- Any required auxiliary data (e.g., an existing album ID if the API expects it).
- The specific test image file placed in a known location or generated on the fly.
Document the source of each data item (e.g., “file small‑jpeg.jpg located in testdata/images/”) so that another engineer can reproduce the setup without guessing.
2.3 Steps and expected result
Write steps as imperatives, each describing a single action or verification. Keep them atomic so that a failure can be pinpointed. After the steps, state the expected result in observable terms (HTTP status code, response JSON fields, UI message, log entry). Avoid vague phrases like “the system behaves correctly”; instead, specify “response body contains { \"url\": \"https://…\" }”.
2.4 Postconditions and cleanup
If the test creates resources (e.g., an uploaded image, a temporary file, a database row), specify how to clean them up. This prevents test‑environment pollution and makes parallel execution safe. For uploads, a typical postcondition is “DELETE the uploaded resource via /api/v1/images/{id} and verify 204 No Content”.
3. Categorizing Test Cases: Positive, Negative, Edge, Boundary
Grouping tests by their intent helps you ensure coverage across the risk spectrum and makes prioritization easier.
3.1 Positive test cases
These verify that the system works when given valid input that satisfies all requirements. They form the baseline confidence that the happy path operates as intended. Examples include uploading a JPEG of exactly 5 MB, a PNG with the minimum allowed dimensions, or a WebP file with an alpha channel.
3.2 Negative test cases (invalid inputs)
Negative cases probe the system’s ability to reject malformed or forbidden data. They should cover each validation rule separately, as well as combinations that might confuse the parser. Typical negative checks are:
- Wrong file extension (upload a
.txtfile with image content). - Correct extension but invalid internal structure (a corrupted JPEG).
- File size just above the limit (10 MB + 1 byte).
- MIME type that does not match extension (send
image/jpegheader with a PNG payload). - Missing required multipart fields (no
filepart). - Non‑image binary (a ZIP archive) sent as
file.
3.3 Edge and boundary cases
Boundary testing focuses on the extremes of numeric limits and special values. For image upload the relevant boundaries are file size, width, height, and sometimes the number of concurrent uploads. Edge cases go beyond simple limits to include unusual file characteristics that can trigger bugs in image‑processing libraries:
- A 1 × 1 pixel PNG (minimum dimension).
- A 5000 × 5000 pixel JPEG at maximum allowed size.
- A GIF with an unusually large frame count (e.g., 1000 frames) that may cause memory exhaustion.
- A WebP file with an ICC profile that triggers a color‑conversion bug.
- An image whose EXIF orientation tag requires a rotation that the library mishandles.
- A file named with Unicode characters or emojis in the filename.
- A zero‑byte file (still passes extension check but contains no image data).
3.4 Security and permission cases
Security‑focused tests verify that the upload endpoint does not become an attack vector. Consider:
- Attempting to upload a file with a path traversal filename like
../../etc/passwd. - Embedding a script (e.g.,
) inside image metadata and checking that it is not reflected in the response. - Sending a file with a double extension (
image.jpg.php) to see if the server treats it as executable. - Verifying that the server responds with appropriate HTTP status codes (403, 403, 401) when the authentication token is missing, expired, or belongs to a user without upload permission.
- Ensuring that uploaded files are stored with non‑executable permissions and that direct URL access returns the image, not a script execution.
4. Building a Test Matrix: 20+ Example Cases
Below is a concrete test matrix that you can import into most test‑case management tool. Each row follows the ID‑Preconditions‑Steps‑Expected Result format described earlier. Feel free to adjust IDs, preconditions, or expected results to match your API contract.
| ID | Preconditions | Steps | Expected Result |
|---|---|---|---|
| IU-001 | User authenticated, valid JWT, endpoint reachable | 1. Select a 2 MB JPEG (testdata/images/valid.jpg).2. Click Upload button. 3. Wait for response. | HTTP 200 OK, JSON { "status":"success", "url":"https://cdn.example.com/uploads/abc123.jpg" }. |
| IU-002 | Same as IU-001 | 1. Select a 9.9 MB PNG (just under limit). 2. Upload. 3. Wait. | HTTP 200 OK, URL returned, image dimensions unchanged. |
| IU-003 | Same as IU-001 | 1. Select a 10 MB GIF (exact limit). 2. Upload. 3. Wait. | HTTP 200 OK, URL returned. |
| IU-004 | Same as IU-001 | 1. Select a 10 MB + 1 byte JPEG. 2. Upload. 3. Wait. | HTTP 413 Payload Too Large, error { "code":"FILE_TOO_LARGE" }. |
| IU-005 | Same as IU-001 | 1. Select a 50 KB file renamed to .jpg but containing plain text “not an image”.2. Upload. 3. Wait. | HTTP 400 Bad Request, error { "code":"INVALID_IMAGE_FORMAT" }. |
| IU-006 | Same as IU-001 | 1. Select a valid JPEG. 2. tamper with its hex header to corrupt the SOI marker. 3. Upload. 3. Wait. | HTTP 400 Bad Request, error { "code":"CORRUPTED_FILE" }. |
| IU-007 | Same as IU-001 | 1. Select a PNG that is 99 × 99 px (below min dimension). 2. Upload. 3. Wait. | HTTP 400 Bad Request, error { "code":"DIMENSION_TOO_SMALL" }. |
| IU-008 | Same as IU-001 | 1. Select a JPEG that is 5001 × 5001 px (above max). 2. Upload. 3. Wait. | HTTP 400 Bad Request, error { "code":"DIMENSION_TOO_LARGE" }. |
| IU-009 | Same as IU-001 | 1. Select a WebP with lossless compression and alpha channel. 2. Upload. 3. Wait. | HTTP 200 OK, URL returned, alpha preserved in stored image. |
| IU-010 | Same as IU-001 | 1. Select a GIF with 200 frames, each 100 × 100 px. 2. Upload. 3. Wait. | HTTP 200 OK, URL returned, first frame extracted as static image (or animated GIF preserved per spec). |
| IU-011 | Same as IU-001 | 1. Select a JPEG with EXIF GPS latitude/longitude. 2. Upload. 3. Wait. | HTTP 200 OK, returned image has EXIF GPS strip‑ped (verify via exiftool). |
| IU-012 | Same as IU-001 | 1. Select a file named 图片.jpeg (Chinese characters).2. Upload. 3. Wait. | HTTP 200 OK, URL returned, filename preserved or safely encoded. |
| IU-013 | Same as IU-001 | 1. Select a zero‑byte file named empty.jpg.2. Upload. 3. Wait. | HTTP 400 Bad Request, error { "code":"EMPTY_FILE" }. |
| IU-014 | Same as IU-001 | 1. Omit the file part in the multipart request (send only metadata).2. Upload. 3. Wait. | HTTP 400 Bad Request, error { "code":"MISSING_FILE_PART" }. |
| IU-015 | Same as IU-001 | 1. Send a valid JPEG but set Content-Type: application/octet-stream.2. Upload. 3. Wait. | HTTP 415 Unsupported Media Type (server sniffing detects mismatch). |
| IU-016 | Same as IU-001 | 1. Filename contains path traversal: ../../evil.jpg.2. Upload. 3. Wait. | HTTP 400 Bad Request or 403 Forbidden, filename sanitized to evil.jpg (no directory escape). |
| IU-017 | Same as IU-001 | 1. Upload a JPEG while providing an invalid auth token (expired). 2. Wait. | HTTP 401 Unauthorized, error { "code":"INVALID_TOKEN" }. |
| IU-018 | Same as IU-001 | 1. Upload a JPEG with a valid token but the user lacks upload:write scope.2. Wait. | HTTP 403 Forbidden, error { "code":"INSUFFICIENT_PERMISSIONS" }. |
| IU-019 | Same as IU-001 | 1. Perform 5 concurrent uploads of 2 MB JPEGs from different sessions. 2. Wait for all responses. | All return HTTP 200 OK, each with unique URL, no 5xx errors. |
| IU-020 | Same as IU-001 | 1. Upload a JPEG, then immediately issue a DELETE to the returned URL’s ID. 2. Verify deletion. | First upload returns 200, DELETE returns 204 No Content, subsequent GET returns 404. |
| IU-021 | Same as IU-001 | 1. Upload a JPEG that contains an embedded XSS payload in a comment block ().2. Wait. | HTTP 200 OK, returned image does not contain the script (metadata stripped). |
| IU-022 | Same as IU-001 | 1. Upload a TIFF file (not in whitelist). 2. Wait. | HTTP 415 Unsupported Media Type, error { "code":"UNSUPPORTED_TYPE" }. |
| IU-023 | Same as IU-001 | 1. Upload a JPEG whose file name is 255 characters long (near filesystem limit). 2. Wait. | HTTP 200 OK, server stores file with a safe truncated or hashed name, URL returned. |
| IU-024 | Same as IU-001 | 1. Upload a JPEG after disabling client‑side JavaScript (to bypass any pre‑checks). 2. Wait. | Server still enforces limits; outcome same as IU-001 or appropriate error if data invalid. |
| IU-025 | Same as IU-001 | 1. Upload a JPEG while simulating a flaky network (50 % packet loss) using tc or toxiproxy.2. Wait. | Either successful upload after retries (if client implements retry) or a clear network‑error message; no partial file left in storage. |
How to use the matrix
- Import the table into your test management tool, map each ID to a requirement (see Section 6).
- Automate the steps using your preferred language; the “Steps” column can be turned into a function that accepts the file path and any variants.
- For cases that need special file generation (corrupted JPEG, oversized GIF, etc.), see Section 5 for data‑setup techniques.
5. Data Setup and Test Environment Preparation
Good test data is the cornerstone of reliable image‑upload testing. Rather than relying on a handful of static files, you should be able to generate images that hit specific boundaries on demand.
5.1 Generating test images of various types and sizes
- ImageMagick – a versatile command‑line tool for creating and manipulating raster images.
To create a JPEG of exact size:
# 2 MB JPEG, 1280×720 (adjust quality to hit target size)
convert -size 1280x720 xc:#fa0 -quality 85 testdata/images/valid.jpg
To verify size:
ls -lh testdata/images/valid.jpg
- ffmpeg – useful for generating animated GIFs or WebP with many frames.
# Create a 100‑frame GIF, each frame 100×100, varying color
ffmpeg -f lavfi -i testsrc=size=100x100:rate=1 -t 10 -vf "scale=100:100,format=rgb24" -loop 0 testdata/images/manyframes.gif
- Python Pillow – for programmatic corruption or embedding metadata.
from PIL import Image
import io
# Create a valid PNG
img = Image.new('RGB', (200, 200), color='red')
buf = io.BytesIO()
img.save(buf, format='PNG')
data = buf.getvalue()
# Corrupt by zeroing the first byte (signature)
corrupted = bytearray(data)
corrupted[0] = 0
with open('testdata/images/corrupted.png', 'wb') as f:
f.write(corrupted)
- Truncache – a small utility to produce files of exact byte length:
# 10 MB + 1 byte file
dd if=/dev/urandom of=testdata/images/overlimit.bin bs=1M count=10
dd if=/dev/urandom of=testdata/images/overlimit.bin bs=1 count=1 seek=10485761 conv=notrunc
mv testdata/images/overlimit.bin testdata/images/overlimit.jpg
Store generated files under a version‑controlled testdata/ directory, and reference them by relative path in your test scripts. Keep a README that documents the command used to produce each file so that others can regenerate if needed.
5.2 Mock servers and API stubs
When you want to test client‑side logic in isolation (e.g., a mobile app that shows a progress bar), you can spin up a lightweight mock endpoint:
- Express.js mock – a few lines to capture multipart and return predetermined responses.
const express = require('express');
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
const app = express();
app.post('/api/v1/upload', upload.single('file'), (req, res) => {
if (!req.file) return res.status(400).send({error:'MISSING_FILE_PART'});
// Simulate size limit
if (req.file.size > 10 * 1024 * 1024) {
return res.status(413).send({error:'FILE_TOO_LARGE'});
}
// Simulate success
res.status(200).send({status:'success', url:`https://mock.example.com/${req.file.filename}`});
});
app.listen(3000, () => console.log('Mock listening on :3000'));
- WireMock – for more sophisticated scenario mapping (e.g., returning 415 for specific MIME types). Define a mapping JSON file:
{
"request": {
"method": "POST",
"urlPath": "/api/v1/upload",
"headers": {
"Content-Type": { "matches": "multipart/form-data;.*" }
}
},
"response": {
"status": 415,
"jsonBody": { "error": "UNSUPPORTED_TYPE" }
}
}
5.3 Using command line tools for quick validation
Before investing in full automation, a quick sanity check with curl or httpie can reveal obvious problems:
# Successful upload
curl -X POST https://api.example.com/api/v1/upload \
-H "Authorization: Bearer $JWT" \
-F "file=@testdata/images/valid.jpg" \
-F "caption=Hello world"
# Oversized file
curl -X POST https://api.example.com/api/v1/upload \
-H "Authorization: Bearer $JWT" \
-F "file=@testdata/images/overlimit.jpg;type=image/jpeg" \
-w "\nHTTP %{http_code}\n"
These commands are handy for exploratory testing, for reproducing a bug reported in production, or for checking that a new server build respects the expected status codes.
6. Prioritization and Traceability
Not all test cases carry the same weight. By linking each case to a requirement and assigning a risk‑based priority, you can focus effort where it matters most and provide evidence to stakeholders that critical paths are covered.
6.1 Risk‑based prioritization (P0, P1, P2)
- P0 (Critical) – Failure would cause data loss, security breach, or block core user journeys (e.g., login‑then‑upload‑avatar). Examples: valid upload under limit, oversized file rejection, missing file part, authentication enforcement.
- P1 (High) – Affects usability or leads to noticeable defects but does not block the primary flow. Examples: dimension validation, EXIF stripping, filename Unicode handling.
- P2 (Medium/Low) – Edge‑case or nice‑to‑have checks. Examples: concurrent upload stress, very long filename, network flakiness.
Assign each test case a priority label in your test management tool. When time is limited, execute all P0s, then as many P1s as possible, and finally sample P2s.
6.2 Linking test cases to requirements (traceability matrix)
Create a simple two‑column table that maps each test case ID to the requirement(s) it validates. This makes impact analysis trivial when a requirement changes.
| Test Case ID | Covered Requirement(s) |
|---|---|
| IU-001 | REQ-UPL-001, REQ-UPL-002, REQ-UPL-009 |
| IU-002 | REQ-UPL-001, REQ-UPL-002 |
| IU-003 | REQ-UPL-001, REQ-UPL-002 |
| IU-004 | REQ-UPL-002 (upper bound) |
| IU-005 | REQ-UPL-004 (extension vs content) |
| IU-006 | REQ-UPL-004 (corrupted content) |
| IU-007 | REQ-UPL-003 (min dimension) |
| IU-008 | REQ-UPL-003 (max dimension) |
| IU-009 | REQ-UPL-001 (WebP support) |
| IU-010 | REQ-UPL-001 (GIF support) |
| IU-011 | REQ-UPL-005 (EXIF strip) |
| IU-012 | REQ-UPL-001 (Unicode filename) |
| IU-013 | REQ-UPL-002 (empty file) |
| IU-014 | REQ-UPL-001 (missing part) |
| IU-015 | REQ-UPL-006 (MIME mismatch) |
| IU-016 | REQ-UPL-004 (path traversal) |
| IU-017 | REQ-UPL-009 (auth) |
| IU-018 | REQ-UPL-009 (authorization) |
| IU-019 | REQ-UPL-002 (concurrency) |
| IU-020 | REQ-UPL-009 (cleanup) |
| IU-021 | REQ-UPL-005 (metadata sanitization) |
| IU-022 | REQ-UPL-001 (unsupported type) |
| IU-023 | REQ-UPL-002 (filename length) |
| IU-024 | REQ-UPL-001 (client‑side bypass) |
| IU-025 | REQ-UPL-002 (network resilience) |
When a requirement is updated (e.g., maximum size increased to 20 MB), you can instantly see which test cases need revision (IU-004, IU-019, etc.) and which new cases must be added.
6.3 Example traceability table for a new feature
Suppose you add a new requirement: REQ-UPL-011 – Generate and return a blurred preview
(128 × 128 px) alongside the full image URL. You would add test cases:
| ID | Preconditions | Steps | Expected Result |
|---|---|---|---|
| IU-026 | Authenticated user, endpoint reachable | 1. Upload a 2 MB JPEG. 2. Wait for response. | HTTP 200 OK, JSON contains { "url":"…", "previewUrl":"…" } where preview image is 128 × 128 px and blurred. |
| IU-027 | Same as IU-026 | 1. Upload a 0.5 MB PNG. 2. Wait. | Preview generated, same dimensions, blur applied. |
| IU-028 | Same as IU-026 | 1. Upload a file exceeding size limit. 2. Wait. | HTTP 413, no preview URL present. |
Add these rows to the traceability matrix, linking them to REQ-UPL-011.
7. Manual Execution vs Automated Scripts
Both manual and automated approaches have merit. Manual testing excels at exploratory scenarios, UI‑level checks, and ad‑hoc investigations. Automation shines for regression, performance, and repeatable validation of the matrix execution.
7.1 When to run manually
- Exploratory checks – trying unusual file names, attempting to embed scripts, or testing how the UI handles error messages.
- Usability validation – confirming that the upload button provides clear feedback, that progress indicators work, and that error toast messages are understandable.
- Ad‑hoc bug verification – when a production incident points to a specific file characteristic (e.g., a particular Photoshop‑saved PSD that caused a crash), you can quickly reproduce it manually before writing an automated test.
7.2 Sample automated test (Python + requests) for happy path
Below is a compact, reusable function that you can plug into a pytest suite. It reads a file from disk
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