@klondikemarlen/expect-http-response 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -6
- package/index.d.mts +15 -0
- package/index.mjs +26 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,81 @@
|
|
|
1
1
|
# @klondikemarlen/expect-http-response
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Readable HTTP response failures for Jest and Vitest: focused status errors and contextual body diffs, without unrelated transport internals. One `expect` describes the status and complete body. This dependency-free ESM matcher is registered explicitly with your existing test runner.
|
|
4
|
+
|
|
5
|
+
## Why This Matcher Exists
|
|
6
|
+
|
|
7
|
+
`toHaveHttpResponse` expresses the response status and complete body as one HTTP contract, with focused diagnostics when that contract fails. Separate status and body assertions provide a useful comparison:
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
expect(response.status).toEqual(200)
|
|
11
|
+
expect(response.body).toEqual(expectedBody)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Those assertions keep an incorrect status obvious and give the body its own native diff. A stock matcher can express the combined contract too:
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
expect(response).toEqual(expect.objectContaining({ status: 200, body: expectedBody }))
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
However, a failure against the whole Supertest response can include unrelated headers, request metadata, and raw response text. `toMatchObject` is not an equivalent complete-body replacement: its partial matching can accept unexpected body fields.
|
|
21
|
+
|
|
22
|
+
`toHaveHttpResponse` combines the status and body checks while keeping their diagnostics focused:
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
expect(response).toHaveHttpResponse(200, expectedBody)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- A status-only failure reports the differing statuses without printing the matching body.
|
|
29
|
+
- A body failure uses the active runner's contextual diff, exposing changed nested values rather than dumping both collections or hiding them as `[Object]`.
|
|
30
|
+
- If both differ, both failures are reported; separate assertions normally stop at the first failed status assertion.
|
|
31
|
+
- Failed negation explains that status and body both matched without printing the matching payload twice.
|
|
32
|
+
|
|
33
|
+
For example, a changed field among 100 response records remains visible in the diff (excerpt):
|
|
34
|
+
|
|
35
|
+
```diff
|
|
36
|
+
"id": 50,
|
|
37
|
+
- "name": "Expected record 50",
|
|
38
|
+
+ "name": "Record 50",
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This changes the diagnostic boundary, not the equality policy. Complete-body comparison, deliberate asymmetric matches, and the runner's custom equality testers retain their existing meaning.
|
|
42
|
+
|
|
43
|
+
### Example: Whole-Response Failure
|
|
44
|
+
|
|
45
|
+
In a real controller test, the expected title of a returned notice was deliberately changed. On Vitest 4.1.11, the whole-response `toEqual(expect.objectContaining(...))` assertion above produced the following selected diff lines. Domain names and fixture values are anonymized; `...` marks omitted output:
|
|
46
|
+
|
|
47
|
+
```diff
|
|
48
|
+
- "body": {
|
|
49
|
+
- "notices": [
|
|
50
|
+
- ObjectContaining {
|
|
51
|
+
- "id": 1,
|
|
52
|
+
- "title": "Scheduled maintenance",
|
|
53
|
+
...
|
|
54
|
+
+ "header": {
|
|
55
|
+
+ "content-type": "application/json; charset=utf-8",
|
|
56
|
+
...
|
|
57
|
+
+ "req": {
|
|
58
|
+
+ "method": "GET",
|
|
59
|
+
+ "url": "http://127.0.0.1:<port>/notices",
|
|
60
|
+
...
|
|
61
|
+
+ "text": "{\"notices\":[...\"title\":\"Active notice\"...]}",
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The expected title appears under `body`, but the received title is embedded in escaped response text among unrelated transport details.
|
|
65
|
+
|
|
66
|
+
Using the same expected body with `toHaveHttpResponse` produced these selected lines instead:
|
|
67
|
+
|
|
68
|
+
```diff
|
|
69
|
+
Body mismatch:
|
|
70
|
+
- Expected
|
|
71
|
+
+ Received
|
|
72
|
+
...
|
|
73
|
+
- "title": "Scheduled maintenance",
|
|
74
|
+
...
|
|
75
|
+
+ "title": "Active notice",
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Both titles are now part of the body comparison, without headers, request metadata, or raw response text. Other body fields can still appear according to the runner's native asymmetric-match formatting.
|
|
4
79
|
|
|
5
80
|
## Installation
|
|
6
81
|
|
|
@@ -74,6 +149,8 @@ export default defineConfig({
|
|
|
74
149
|
|
|
75
150
|
Import the matcher from the package root and extend the active runner's `expect` instance explicitly. The matcher relies on the runner's equality function and custom matcher context; it does not bundle or select a runner.
|
|
76
151
|
|
|
152
|
+
Focused failures use the runner's `utils.printDiffOrStringify`, which Jest and Vitest supply through `expect.extend`. Other compatible runners need a Jest- or Vitest-compatible version of that utility for contextual diffs; without it, only basic expected/received rendering is available.
|
|
153
|
+
|
|
77
154
|
## TypeScript
|
|
78
155
|
|
|
79
156
|
The package root resolves to `index.d.mts`. Add a declaration augmentation for each runner you actually install. For Jest's `expect` package:
|
|
@@ -174,17 +251,17 @@ npm test
|
|
|
174
251
|
npm pack --dry-run
|
|
175
252
|
git switch main
|
|
176
253
|
git pull --ff-only origin main
|
|
177
|
-
git tag v1.0.
|
|
178
|
-
git push origin v1.0.
|
|
179
|
-
gh release create v1.0.
|
|
254
|
+
git tag v1.0.2
|
|
255
|
+
git push origin v1.0.2
|
|
256
|
+
gh release create v1.0.2 --title v1.0.2 --generate-notes
|
|
180
257
|
npm publish --access public
|
|
181
|
-
npm view @klondikemarlen/expect-http-response@1.0.
|
|
258
|
+
npm view @klondikemarlen/expect-http-response@1.0.2 version
|
|
182
259
|
```
|
|
183
260
|
|
|
184
261
|
In a separate consumer project, install the registry artifact:
|
|
185
262
|
|
|
186
263
|
```sh
|
|
187
|
-
npm install --save-dev @klondikemarlen/expect-http-response@1.0.
|
|
264
|
+
npm install --save-dev @klondikemarlen/expect-http-response@1.0.2
|
|
188
265
|
```
|
|
189
266
|
|
|
190
267
|
After publishing, use the actual registry installation in a fresh consumer project and run its matcher and TypeScript registration checks. Do not claim npm availability until the publish and registry install have been observed.
|
package/index.d.mts
CHANGED
|
@@ -12,7 +12,22 @@ interface HttpResponseLike {
|
|
|
12
12
|
readonly body?: unknown
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
type JestBodyFormatter = (
|
|
16
|
+
expected: unknown,
|
|
17
|
+
received: unknown,
|
|
18
|
+
expectedLabel: string,
|
|
19
|
+
receivedLabel: string,
|
|
20
|
+
expand: boolean
|
|
21
|
+
) => string
|
|
22
|
+
|
|
23
|
+
type VitestBodyFormatter = (
|
|
24
|
+
received: unknown,
|
|
25
|
+
expected: unknown,
|
|
26
|
+
options?: { expand?: boolean }
|
|
27
|
+
) => string | undefined
|
|
28
|
+
|
|
15
29
|
interface MatcherUtils {
|
|
30
|
+
printDiffOrStringify?: JestBodyFormatter | VitestBodyFormatter
|
|
16
31
|
matcherHint?: (
|
|
17
32
|
matcherName: string,
|
|
18
33
|
received?: string,
|
package/index.mjs
CHANGED
|
@@ -58,10 +58,8 @@ function formatMessage(context, result) {
|
|
|
58
58
|
hint,
|
|
59
59
|
"",
|
|
60
60
|
"Expected the response not to match this HTTP contract:",
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
`Expected body: ${printValue(context, "printExpected", result.expectedBody)}`,
|
|
64
|
-
`Received body: ${printValue(context, "printReceived", result.receivedBody)}`,
|
|
61
|
+
`Matched status: ${printValue(context, "printReceived", result.receivedStatus)}`,
|
|
62
|
+
"The status and body both matched.",
|
|
65
63
|
].join("\n")
|
|
66
64
|
}
|
|
67
65
|
|
|
@@ -77,14 +75,36 @@ function formatMessage(context, result) {
|
|
|
77
75
|
lines.push(
|
|
78
76
|
"",
|
|
79
77
|
"Body mismatch:",
|
|
80
|
-
|
|
81
|
-
`Received body: ${printValue(context, "printReceived", result.receivedBody)}`
|
|
78
|
+
formatBodyDifference(context, result.expectedBody, result.receivedBody)
|
|
82
79
|
)
|
|
83
80
|
}
|
|
84
81
|
|
|
85
82
|
return lines.join("\n")
|
|
86
83
|
}
|
|
87
84
|
|
|
85
|
+
function formatBodyDifference(context, expectedBody, receivedBody) {
|
|
86
|
+
const formatter = context.utils?.printDiffOrStringify
|
|
87
|
+
let difference
|
|
88
|
+
|
|
89
|
+
if (typeof formatter === "function") {
|
|
90
|
+
// Jest uses five positional arguments; Vitest uses three or four, with reversed values.
|
|
91
|
+
if (formatter.length >= 5) {
|
|
92
|
+
difference = formatter(expectedBody, receivedBody, "Expected body", "Received body", false)
|
|
93
|
+
} else {
|
|
94
|
+
difference = formatter(receivedBody, expectedBody, { expand: false })
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (typeof difference === "string") {
|
|
99
|
+
return difference
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return [
|
|
103
|
+
`Expected body: ${printValue(context, "printExpected", expectedBody)}`,
|
|
104
|
+
`Received body: ${printValue(context, "printReceived", receivedBody)}`,
|
|
105
|
+
].join("\n")
|
|
106
|
+
}
|
|
107
|
+
|
|
88
108
|
function matcherHint(context) {
|
|
89
109
|
if (typeof context.utils?.matcherHint === "function") {
|
|
90
110
|
return context.utils.matcherHint(
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klondikemarlen/expect-http-response",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Focused HTTP response failure diagnostics for Jest and Vitest",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|