intermediate17 min

Validating Error Responses

An error response deserves the same careful scrutiny as a success response — the right status code, a genuinely useful body, and no leaked internals.

What you'll learn

  • Design assertions for an error response's status, structure, and message content
  • Identify when an error response leaks internal implementation details
  • Explain why consistent error shape across endpoints matters for API consumers

Prerequisites

Explanation

A success response gets carefully checked against a schema, exact field values, and status code. An error response, in far too many test suites, gets a single lazy check: "is the status not 200?" That's a real gap — error responses are exactly as testable, and exactly as capable of hiding real defects, as success responses are.

A well-designed error response deserves three specific checks. The status code should be the correct, specific one for the situation (from earlier lessons: 400 for bad input, 401 for missing authentication, 403 for missing authorization, 404 for a resource that doesn't exist, 409 for a conflict like the stale-data case from the previous lesson) — not a generic 400 or 500 for every possible failure. The body's structure should be consistent and machine-readable: does every endpoint's error response use the same shape (say, { "error": { "code": "...", "message": "..." } }), or does one endpoint return a plain string while another returns a nested object? Inconsistency here quietly breaks any client code trying to handle errors generically across the whole API. The message content should be genuinely useful to whoever's debugging — specific enough to act on ("email is required") rather than generic to the point of uselessness ("an error occurred").

There's a security dimension too, echoing the defect-reporting lesson from Software Testing Foundations: an error response should never leak internal details a real attacker could use — a full stack trace, an internal file path, a raw database error message, or (subtly) confirmation of whether a specific username or email exists in the system just from how the error is worded differently for "wrong password" versus "no such account." A tester validating error responses is doing double duty: confirming the API is genuinely useful to legitimate callers, and confirming it isn't accidentally useful to attackers.

Example

A simulated error response checked for status, structure, message quality, and the absence of leaked internals.

const errorResponse = {
  status: 400,
  body: { error: { code: "MISSING_FIELD", message: "email is required" } },
};

function isWellFormedError(res) {
  const hasCorrectStatus = res.status >= 400 && res.status < 500;
  const hasStructuredBody = typeof res.body?.error?.code === "string" && typeof res.body?.error?.message === "string";
  const leaksInternals = /stack|trace|\/usr\/|select /i.test(JSON.stringify(res.body));
  return hasCorrectStatus && hasStructuredBody && !leaksInternals;
}

console.log(isWellFormedError(errorResponse)); // true

Try it yourself

Change the message to something that leaks a stack trace and re-run to see the check fail.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

Two endpoints return errors in different shapes: endpointA returns { error: { code, message } }, endpointB returns just a plain string. Set isConsistentShape to reflect whether these two endpoints use a consistent error structure.

Checks: correctly identifies the inconsistency

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

Write a function leaksInternalDetails(errorBody) that returns true if JSON.stringify(errorBody) case-insensitively contains any of: 'stack trace', a file path pattern like '/usr/' or 'c:\\', or 'select ' (a leaked SQL query fragment).

Checks: a clean error message is not flagged · a leaked stack trace and path are flagged · a leaked SQL query is flagged

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Common mistakes

  • Checking only that an error response's status code is "not 200," without verifying it's the specific, correct error code for the situation.
  • Using a different error body shape on different endpoints, breaking any client code that tries to handle errors generically.
  • Never testing error responses for leaked internal details, treating that concern as security-team-only rather than something any API tester can check.

Knowledge check

Knowledge check

1. Why is checking only "the status code is not 200" an insufficient test for an error response?
2. Why does consistent error shape across different endpoints of the same API matter?
3. What security-relevant check should a tester apply to error messages?

Takeaway

Error responses deserve the same rigor as success responses: the specific correct status code, a consistent structured body, a genuinely useful message, and no leaked internal details.

Summary

This lesson covered validating error responses for correct status codes, consistent structure across endpoints, and the absence of leaked internal implementation details.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.