In Postman's sandbox, what is `pm.response.to` mechanically, and where do its matchers come from?
answer
- It is a property, but not a stored one
- Reading it builds something new each time
- The same thing pm.expect would give you
- A chai plugin supplies the reply matchers
- Serialising the response drops the helper
basics
~10 sIn Postman's sandbox, pm.response.to is a getter that returns chai's expect(this).to for that response. The matchers hanging off it come from the chai-postman plugin the sandbox registers into chai, not from chai's core.
solid answer
~40 sThe sandbox defines `to` on the response object as a **getter** whose body returns `chai.expect(this).to`. So `pm.response.to.have.status(200)` is exactly `pm.expect(pm.response).to.have.status(200)` — `pm.expect` is chai's `expect`, and every read of `to` builds a **fresh** assertion, which is why chains never share accumulated state. `pm.request.to` is defined the same way, giving `pm.request.to.have.header('host')`. The response-shaped matchers (`status`, `statusCodeClass`, `header`, `body`, `jsonBody`, `responseTime`) are not chai's; the sandbox registers the `chai-postman` plugin into chai during initialisation and they arrive with it. Because `to` is a sandbox addition rather than part of the SDK's `Response`, the sandbox also overrides `toJSON` to delete it before serialising and put it back afterwards.
code
javascript · 3 linespm.response.to.have.status(200);
pm.expect(pm.response).to.have.status(200);
pm.request.to.have.header('host');go deeper
Recall that pm.response.to starts an assertion and that pm.expect(pm.response).to writes the same thing. Know that to is read, never called with parentheses.
Explain that to is a getter returning chai's expect for the response, so each read yields a fresh assertion, and that the reply matchers come from a chai plugin the sandbox registers rather than from chai itself.
Use the attribution to debug faster: chai owns the grammar and the message format, the plugin owns the matcher vocabulary, the SDK owns the response object. Knowing which layer failed is what shortens the investigation.
Own where assertion vocabulary should live across a suite. Custom helpers in scripts are copies nobody upgrades, so decide what the standard chain covers and what genuinely warrants a shared, reviewed extension.
## `to` is a getter, not a stored object In a Postman script, `pm.response` is the reply wrapped as the SDK's `Response`. That class has no assertion surface of its own — the sandbox adds one. When it builds the `pm` API, it defines a `to` property on the response with a getter whose body is a single expression: return chai's `expect` applied to that response, then take its `to`. In other words: ```javascript // what the sandbox effectively installs on pm.response Object.defineProperty(pm.response, 'to', { get () { return chai.expect(this).to; } }); ``` Everything about how the chain reads follows from that one line. `have`, `be` and `not` are chai's own language chains, so negation, grouping and message formatting are chai's behaviour, not something Postman reimplemented. ## Two spellings, one assertion Because `pm.expect` is chai's `expect`, these are the same assertion: ```javascript pm.response.to.have.status(200); pm.expect(pm.response).to.have.status(200); ``` The first is shorter and reads better inside a suite; the second is what you fall back to when the subject is not the response — a value you pulled out of the payload, say. Knowing they are the same removes a lot of guesswork about which matchers are available where. `pm.request.to` is installed by the same code path, which is why request-side assertions such as `pm.request.to.have.header('host')` work and read identically. ## Why "getter" matters | Property built as | Consequence in a script | |---|---| | a stored object, built once | every chain shares one assertion's state | | a getter, evaluated per read | each read starts a clean assertion | Because `to` is evaluated on every read, two assertions written one after another cannot leak flags into each other, and a negated chain does not leave `not` switched on for the next line. It also means `to` is cheap to *have* and only costs anything when you actually touch it. One practical consequence: `to` is read as a property, never invoked. `pm.response.to()` is not a thing. ## Where the matchers come from chai out of the box knows nothing about HTTP replies. The sandbox registers a plugin — `chai-postman` — into chai during initialisation, and that plugin contributes the reply-shaped vocabulary: - `status`, `statusCode`, `statusReason` and `statusCodeClass` on the status line; - `header(key)` and `header(key, value)` on the headers; - `body()` on the raw text, `jsonBody(...)` on the parsed payload; - `responseTime` on the recorded duration; - object-kind checks such as `postmanResponse` and `postmanRequest`, used to fail fast when the subject is not the shape a matcher expects. Attribution is worth keeping straight in an interview: `pm.*` is the **sandbox's** surface, `Response` and `Request` are the **SDK's** classes, and the matchers arrive from a chai plugin the sandbox installs. Three different owners, one chain. ## The serialisation wrinkle Adding a helper property to an SDK object has a side effect: it would show up whenever that object is serialised, and a serialised response crosses the boundary out of the sandbox. So the sandbox also patches `toJSON` on both `Response` and `Request` to delete `to` before the underlying serialisation runs and restore it afterwards. The visible result is that a serialised response has no `to` key even though the live object does. If you ever debug by dumping `pm.response`, do not conclude from the absence of `to` that the assertion surface is missing — it was removed on the way out on purpose. ## What this buys you when a test misbehaves 1. **An unknown-matcher error is a plugin question, not a chai question.** If a name is not recognised, you are reaching for something the plugin does not contribute. 2. **A confusing failure message is chai's format.** The subject, the negation and the wording come from chai; the matcher only supplied the comparison. 3. **When the subject is not a response, switch spellings.** Use `pm.expect(value)` on an extracted value rather than trying to bend the response chain around it. 4. **Do not cache `pm.response.to` in a variable** and reuse it across assertions. Read it fresh each time; that is what the getter is for.
- If `pm.response.to` and `pm.expect(pm.response).to` are the same thing, when would you write the longer form?Whenever the subject is not the response itself. `pm.expect` takes any value, so you use it on something pulled out of the payload, a header value, or a computed number. The response chain only helps when the response is the subject; the reply-shaped matchers expect a response object and will complain otherwise.
- Why does a serialised `pm.response` carry no `to` key even though the live object has one?The sandbox added `to` to an SDK object, so it patches `toJSON` on `Response` and `Request` to delete the property before serialising and restore it afterwards. The absence in serialised output is deliberate housekeeping, not evidence that the assertion surface is missing.
saying these in an interview costs you the question
- Thinks to is a method you call, as to()
- Believes the SDK's Response class declares to itself
- Assumes chai ships the status and jsonBody matchers
- Caches pm.response.to and reuses it across assertions
- Cannot say that pm.expect is chai's expect