skip to content

In a Postman test script, how does a throw inside a pm.test block differ from a throw outside one?

level: middleimportance: must knowfreq 68%

answer

  1. The block owns a try/catch
  2. Top level has no wrapper
  3. One is an assertion, one an execution error
  4. Unguarded parsing kills every later block
  5. Containment protects script from block only

basics

~20 s

Containment. A throw inside pm.test is caught by that block: the block is recorded as failed and the script keeps going. A throw outside any block escapes, ends the script execution and surfaces as an execution error, not an assertion.

solid answer

~40 s

`pm.test(name, fn)` runs `fn` inside a try/catch. A throw there is caught, the block's record gets `passed: false` with the thrown value in its `error` slot, the record is still emitted, and the **next statement in the script runs normally** — later blocks are unaffected. A throw at script top level has no such wrapper: it unwinds the whole execution, which the sandbox reports as an execution error rather than as a failed assertion. The practical consequence is that setup code outside blocks is fragile. `pm.response.json()` on a non-JSON body, or reading a property of an undefined variable, will abort every remaining check if you do it between blocks instead of inside one.

code

javascript · 5 lines
javascript
const body = pm.response.json();

pm.test('has an id', function () {
    pm.expect(body.id).to.be.a('string');
});

go deeper

for a junior

Recall that checks belong inside pm.test(name, fn). Know that a failure there is recorded and the script carries on, while an error outside any block stops everything.

for a middle

Explain the try/catch the block provides, what the failed record carries, and why an execution error has no name or index. Name the setup lines that typically throw at top level.

for a senior

Diagnose a run showing an execution error and no assertions, and prescribe the fix: move risky parsing into a block or guard it, rather than wrapping the whole script.

for a principal

Set the house rule for where setup lives across a collection, so a bad reply from one service degrades to a single named failure instead of erasing a whole script's report.

## Two different error paths A Postman script has two places an error can land, and they behave nothing alike. Inside `pm.test(name, fn)`, the sandbox runs `fn` in a try/catch of its own. When the body throws, the catch marks that block's record `passed: false`, stores the thrown value in the record's `error` field, and emits the record anyway. Control then returns to the line after the `pm.test(...)` call. The script is not interrupted. Outside any block — at the top level of the script — there is no such wrapper. A throw propagates out of the script body, ends the execution, and is dispatched as an **execution error**. It is not an assertion result: it has no name, no index, and no pass/fail flag, because no block ever existed to hold it. | | throw inside `pm.test` | throw at script top level | |---|---|---| | caught by | the block's own try/catch | nothing in the script | | recorded as | one failed assertion record | an execution error | | carries a name | yes, the block's name | no | | gets an `index` | yes | no | | rest of the script | keeps running | does not run | | later `pm.test` blocks | still run and report | never created | ## Why this is the point of wrapping checks The containment is the reason to put every claim inside a block rather than writing bare `pm.expect` calls in sequence. A bare expectation that fails takes the whole script with it, and everything you would have learned from the remaining checks is lost. The same expectation inside a block costs you exactly one red line and nothing else. So the failure mode to watch for is not the assertion — it is the **setup** you leave outside: - `const body = pm.response.json();` at the top of the script throws on an HTML error page, and no block runs at all. - `const id = body.data.id;` throws a TypeError when `data` is absent, with the same effect. - A `JSON.parse` of a variable that was never set does it too. In each case the run reports one execution error and zero assertions — which reads, to anyone skimming, like a script that did not exist rather than a service that misbehaved. ## Making setup safe There are two honest ways to keep top-level code from erasing the report: 1. **Pull the risky parsing inside a block.** Parse the body inside the first `pm.test` and assert on it there. If parsing fails, that block fails with a readable name and every later block still runs and reports. 2. **Guard it.** Read defensively at top level — optional access, a default value, a `try`/`catch` you own — so that a missing field produces a failing assertion rather than a dead script. Both are better than the third option people reach for, which is wrapping the whole script in one giant block. That trades a dead script for a single opaque red line and loses every other verdict along the way. ## What a reader sees afterwards The distinction matters most when someone else reads the run. A failed block is a claim that was made and disproved; its name says what was expected. An execution error is a script that fell over; nobody can tell from it whether the service was healthy, because no claim was ever evaluated. When a report shows an execution error, the first question is always "what did the script do before any block ran", and the answer is almost always an unguarded parse or property read. ## The asymmetry is one-directional One last detail worth stating plainly: containment protects the script from the block, not the block from the script. A failing block cannot stop the ones after it, but anything that goes wrong between blocks can stop all of them. That asymmetry is why the useful habit is "put code inside blocks by default, and only hoist to top level what you are sure cannot throw".

  • A run reports one execution error and zero assertions. What do you look at first?
    The top of the script, before the first `pm.test` call. Zero assertions means nothing threw inside a block — a block would have emitted a failed record. The usual cause is unguarded setup: parsing a body that is not JSON, or reading a property of an undefined value. Move that work inside a block or guard it.
  • Does a failed pm.test block stop the blocks after it in the same script?
    No. The throw is caught by that block, its record is emitted with `passed: false`, and the very next statement runs. Every later block is created and reported normally. Only an error escaping to script top level ends the execution, and only a run-level setting decides whether a failure stops the wider sequence.

saying these in an interview costs you the question

  • Says a failing block aborts the remaining blocks
  • Treats an execution error as just another failed test
  • Parses the response body at top level without guarding
  • Wraps the entire script in one pm.test block
  • Assumes bare pm.expect calls report like blocks do