How to Write Test Cases for Image Upload (With Examples)

How to Write Test Cases for Image Upload (With Examples)

January 10, 2026 · 15 min read · How-To Guides

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:

  1. 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).
  2. Client‑side validation – optional JavaScript validates MIME type, extension, or dimensions before sending data.
  3. Request formation – the file is packaged in a multipart/form-data body, often together with metadata such as a caption, album ID, or user token.
  4. Network transmission – the HTTP POST (or PUT) is sent to an endpoint like /api/v1/upload.
  5. Server‑side receipt – the backend parses the multipart stream, saves the raw bytes to a temporary location, and begins validation.
  6. 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.
  7. Processing – if validation passes, the image may be resized, converted to a standard format, stripped of EXIF data, or have watermarks applied.
  8. 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.
  9. 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 IDDescription
REQ-UPL-001Accept JPEG, PNG, GIF, WebP files only.
REQ-UPL-002Maximum file size 10 MB.
REQ-UPL-003Minimum dimensions 100 × 100 px; maximum 5000 × 5000 px.
REQ-UPL-004Reject files with mismatched extension and actual content (e.g., .jpg that is a ZIP).
REQ-UPL-005Strip EXIF GPS coordinates for privacy.
REQ-UPL-006Return a 415 Unsupported Media Type for disallowed MIME types.
REQ-UPL-007Return a 413 Payload Too Large for oversized files.
REQ-UPL-008Store the processed image in S3 bucket user‑uploads with public‑read ACL.
REQ-UPL-009Log upload attempts (user ID, filename, size, outcome) to audit table.
REQ-UPL-010Ensure 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:

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:

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:

3.4 Security and permission cases

Security‑focused tests verify that the upload endpoint does not become an attack vector. Consider:

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.

IDPreconditionsStepsExpected Result
IU-001User authenticated, valid JWT, endpoint reachable1. 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-002Same as IU-0011. Select a 9.9 MB PNG (just under limit).
2. Upload.
3. Wait.
HTTP 200 OK, URL returned, image dimensions unchanged.
IU-003Same as IU-0011. Select a 10 MB GIF (exact limit).
2. Upload.
3. Wait.
HTTP 200 OK, URL returned.
IU-004Same as IU-0011. Select a 10 MB + 1 byte JPEG.
2. Upload.
3. Wait.
HTTP 413 Payload Too Large, error { "code":"FILE_TOO_LARGE" }.
IU-005Same as IU-0011. 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-006Same as IU-0011. 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-007Same as IU-0011. 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-008Same as IU-0011. Select a JPEG that is 5001 × 5001 px (above max).
2. Upload.
3. Wait.
HTTP 400 Bad Request, error { "code":"DIMENSION_TOO_LARGE" }.
IU-009Same as IU-0011. Select a WebP with lossless compression and alpha channel.
2. Upload.
3. Wait.
HTTP 200 OK, URL returned, alpha preserved in stored image.
IU-010Same as IU-0011. 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-011Same as IU-0011. 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-012Same as IU-0011. Select a file named 图片.jpeg (Chinese characters).
2. Upload.
3. Wait.
HTTP 200 OK, URL returned, filename preserved or safely encoded.
IU-013Same as IU-0011. Select a zero‑byte file named empty.jpg.
2. Upload.
3. Wait.
HTTP 400 Bad Request, error { "code":"EMPTY_FILE" }.
IU-014Same as IU-0011. 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-015Same as IU-0011. 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-016Same as IU-0011. 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-017Same as IU-0011. Upload a JPEG while providing an invalid auth token (expired).
2. Wait.
HTTP 401 Unauthorized, error { "code":"INVALID_TOKEN" }.
IU-018Same as IU-0011. Upload a JPEG with a valid token but the user lacks upload:write scope.
2. Wait.
HTTP 403 Forbidden, error { "code":"INSUFFICIENT_PERMISSIONS" }.
IU-019Same as IU-0011. 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-020Same as IU-0011. 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-021Same as IU-0011. 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-022Same as IU-0011. Upload a TIFF file (not in whitelist).
2. Wait.
HTTP 415 Unsupported Media Type, error { "code":"UNSUPPORTED_TYPE" }.
IU-023Same as IU-0011. 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-024Same as IU-0011. 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-025Same as IU-0011. 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

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

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

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:

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)

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 IDCovered Requirement(s)
IU-001REQ-UPL-001, REQ-UPL-002, REQ-UPL-009
IU-002REQ-UPL-001, REQ-UPL-002
IU-003REQ-UPL-001, REQ-UPL-002
IU-004REQ-UPL-002 (upper bound)
IU-005REQ-UPL-004 (extension vs content)
IU-006REQ-UPL-004 (corrupted content)
IU-007REQ-UPL-003 (min dimension)
IU-008REQ-UPL-003 (max dimension)
IU-009REQ-UPL-001 (WebP support)
IU-010REQ-UPL-001 (GIF support)
IU-011REQ-UPL-005 (EXIF strip)
IU-012REQ-UPL-001 (Unicode filename)
IU-013REQ-UPL-002 (empty file)
IU-014REQ-UPL-001 (missing part)
IU-015REQ-UPL-006 (MIME mismatch)
IU-016REQ-UPL-004 (path traversal)
IU-017REQ-UPL-009 (auth)
IU-018REQ-UPL-009 (authorization)
IU-019REQ-UPL-002 (concurrency)
IU-020REQ-UPL-009 (cleanup)
IU-021REQ-UPL-005 (metadata sanitization)
IU-022REQ-UPL-001 (unsupported type)
IU-023REQ-UPL-002 (filename length)
IU-024REQ-UPL-001 (client‑side bypass)
IU-025REQ-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:

IDPreconditionsStepsExpected Result
IU-026Authenticated user, endpoint reachable1. 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-027Same as IU-0261. Upload a 0.5 MB PNG.
2. Wait.
Preview generated, same dimensions, blur applied.
IU-028Same as IU-0261. 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

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