Skip to content

Commit ac30972

Browse files
hi-ogawasheremet-va
andauthoredNov 2, 2023
fix: apply serializer to Error instance for thrown snapshot (#4396)
Co-authored-by: Vladimir <sleuths.slews0s@icloud.com>
1 parent 9fe3873 commit ac30972

File tree

13 files changed

+148
-44
lines changed

13 files changed

+148
-44
lines changed
 

Diff for: ‎docs/api/expect.md

-4
Original file line numberDiff line numberDiff line change
@@ -748,16 +748,12 @@ Note that since file system operation is async, you need to use `await` with `to
748748

749749
The same as [`toMatchSnapshot`](#tomatchsnapshot), but expects the same value as [`toThrowError`](#tothrowerror).
750750

751-
If the function throws an `Error`, the snapshot will be the error message. Otherwise, snapshot will be the value thrown by the function.
752-
753751
## toThrowErrorMatchingInlineSnapshot
754752

755753
- **Type:** `(snapshot?: string, message?: string) => void`
756754

757755
The same as [`toMatchInlineSnapshot`](#tomatchinlinesnapshot), but expects the same value as [`toThrowError`](#tothrowerror).
758756

759-
If the function throws an `Error`, the snapshot will be the error message. Otherwise, snapshot will be the value thrown by the function.
760-
761757
## toHaveBeenCalled
762758

763759
- **Type:** `() => Awaitable<void>`

Diff for: ‎docs/guide/snapshot.md

+28-1
Original file line numberDiff line numberDiff line change
@@ -237,5 +237,32 @@ exports[`toThrowErrorMatchingSnapshot: hint 1`] = `"error"`;
237237

238238
In Vitest, the equivalent snapshot will be:
239239
```console
240-
exports[`toThrowErrorMatchingSnapshot > hint 1`] = `"error"`;
240+
exports[`toThrowErrorMatchingSnapshot > hint 1`] = `[Error: error]`;
241+
```
242+
243+
#### 4. default `Error` snapshot is different for `toThrowErrorMatchingSnapshot` and `toThrowErrorMatchingInlineSnapshot`
244+
245+
```js
246+
test('snapshot', () => {
247+
//
248+
// in Jest
249+
//
250+
251+
expect(new Error('error')).toMatchInlineSnapshot(`[Error: error]`)
252+
253+
// Jest snapshots `Error.message` for `Error` instance
254+
expect(() => {
255+
throw new Error('error')
256+
}).toThrowErrorMatchingInlineSnapshot(`"error"`)
257+
258+
//
259+
// in Vitest
260+
//
261+
262+
expect(new Error('error')).toMatchInlineSnapshot(`[Error: error]`)
263+
264+
expect(() => {
265+
throw new Error('error')
266+
}).toThrowErrorMatchingInlineSnapshot(`[Error: error]`)
267+
})
241268
```

Diff for: ‎examples/mocks/test/error-mock.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ vi.mock('../src/default', () => {
44

55
test('when using top level variable, gives helpful message', async () => {
66
await expect(() => import('../src/default').then(m => m.default)).rejects
7-
.toThrowErrorMatchingInlineSnapshot('"[vitest] There was an error when mocking a module. If you are using "vi.mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file. Read more: https://vitest.dev/api/vi.html#vi-mock"')
7+
.toThrowErrorMatchingInlineSnapshot(`[Error: [vitest] There was an error when mocking a module. If you are using "vi.mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file. Read more: https://vitest.dev/api/vi.html#vi-mock]`)
88
})

Diff for: ‎packages/vitest/src/integrations/snapshot/chai.ts

+5-12
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,20 @@ export function getSnapshotClient(): SnapshotClient {
1818
return _client
1919
}
2020

21-
function getErrorMessage(err: unknown) {
22-
if (err instanceof Error)
23-
return err.message
24-
25-
return err
26-
}
27-
28-
function getErrorString(expected: () => void | Error, promise: string | undefined) {
21+
function getError(expected: () => void | Error, promise: string | undefined) {
2922
if (typeof expected !== 'function') {
3023
if (!promise)
3124
throw new Error(`expected must be a function, received ${typeof expected}`)
3225

3326
// when "promised", it receives thrown error
34-
return getErrorMessage(expected)
27+
return expected
3528
}
3629

3730
try {
3831
expected()
3932
}
4033
catch (e) {
41-
return getErrorMessage(e)
34+
return e
4235
}
4336

4437
throw new Error('snapshot function didn\'t throw')
@@ -141,7 +134,7 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => {
141134
const promise = utils.flag(this, 'promise') as string | undefined
142135
const errorMessage = utils.flag(this, 'message')
143136
getSnapshotClient().assert({
144-
received: getErrorString(expected, promise),
137+
received: getError(expected, promise),
145138
message,
146139
errorMessage,
147140
...getTestNames(test),
@@ -162,7 +155,7 @@ export const SnapshotPlugin: ChaiPlugin = (chai, utils) => {
162155
const errorMessage = utils.flag(this, 'message')
163156

164157
getSnapshotClient().assert({
165-
received: getErrorString(expected, promise),
158+
received: getError(expected, promise),
166159
message,
167160
inlineSnapshot,
168161
isInline: true,

Diff for: ‎test/core/test/__snapshots__/mocked.test.ts.snap

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

33
exports[`mocked function which fails on toReturnWith > just one call 1`] = `
4-
"expected "spy" to return with: 2 at least once
4+
[AssertionError: expected "spy" to return with: 2 at least once
55
66
Received:
77
@@ -12,11 +12,11 @@ Received:
1212
1313
1414
Number of calls: 1
15-
"
15+
]
1616
`;
1717

1818
exports[`mocked function which fails on toReturnWith > multi calls 1`] = `
19-
"expected "spy" to return with: 2 at least once
19+
[AssertionError: expected "spy" to return with: 2 at least once
2020
2121
Received:
2222
@@ -37,11 +37,11 @@ Received:
3737
3838
3939
Number of calls: 3
40-
"
40+
]
4141
`;
4242

4343
exports[`mocked function which fails on toReturnWith > oject type 1`] = `
44-
"expected "spy" to return with: { a: '4' } at least once
44+
[AssertionError: expected "spy" to return with: { a: '4' } at least once
4545
4646
Received:
4747
@@ -68,16 +68,16 @@ Received:
6868
6969
7070
Number of calls: 3
71-
"
71+
]
7272
`;
7373

7474
exports[`mocked function which fails on toReturnWith > zero call 1`] = `
75-
"expected "spy" to return with: 2 at least once
75+
[AssertionError: expected "spy" to return with: 2 at least once
7676
7777
Received:
7878
7979
8080
8181
Number of calls: 0
82-
"
82+
]
8383
`;

Diff for: ‎test/core/test/__snapshots__/snapshot.test.ts.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ exports[`single line snapshot 6`] = `"some string'"`;
6060
6161
exports[`single line snapshot 7`] = `"some 'string'"`;
6262
63-
exports[`throwing 1`] = `"omega"`;
63+
exports[`throwing 1`] = `[Error: omega]`;
6464
6565
exports[`throwing 2`] = `"omega"`;
6666
@@ -70,7 +70,7 @@ exports[`throwing 3`] = `
7070
}
7171
`;
7272
73-
exports[`throwing 4`] = `"omega"`;
73+
exports[`throwing 4`] = `[Error: omega]`;
7474
7575
exports[`with big array 1`] = `
7676
{

Diff for: ‎test/core/test/jest-expect.test.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ describe('jest-expect', () => {
167167
}).toEqual({
168168
sum: expect.closeTo(0.4),
169169
})
170-
}).toThrowErrorMatchingInlineSnapshot(`"expected { sum: 0.30000000000000004 } to deeply equal { sum: CloseTo{ …(4) } }"`)
170+
}).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected { sum: 0.30000000000000004 } to deeply equal { sum: CloseTo{ …(4) } }]`)
171171

172172
// TODO: support set
173173
// expect(new Set(['bar'])).not.toEqual(new Set([expect.stringContaining('zoo')]))
@@ -302,14 +302,14 @@ describe('jest-expect', () => {
302302

303303
expect(() => {
304304
expect(complex).toHaveProperty('a-b', false)
305-
}).toThrowErrorMatchingInlineSnapshot('"expected { \'0\': \'zero\', foo: 1, …(4) } to have property "a-b" with value false"')
305+
}).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected { '0': 'zero', foo: 1, …(4) } to have property "a-b" with value false]`)
306306

307307
expect(() => {
308308
const x = { a: { b: { c: 1 } } }
309309
const y = { a: { b: { c: 2 } } }
310310
Object.freeze(x.a)
311311
expect(x).toEqual(y)
312-
}).toThrowErrorMatchingInlineSnapshot(`"expected { a: { b: { c: 1 } } } to deeply equal { a: { b: { c: 2 } } }"`)
312+
}).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected { a: { b: { c: 1 } } } to deeply equal { a: { b: { c: 2 } } }]`)
313313
})
314314

315315
it('assertions', () => {
@@ -406,14 +406,14 @@ describe('jest-expect', () => {
406406
expect(() => {
407407
expect(() => {
408408
}).toThrow(Error)
409-
}).toThrowErrorMatchingInlineSnapshot('"expected function to throw an error, but it didn\'t"')
409+
}).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected function to throw an error, but it didn't]`)
410410
})
411411

412412
it('async wasn\'t awaited', () => {
413413
expect(() => {
414414
expect(async () => {
415415
}).toThrow(Error)
416-
}).toThrowErrorMatchingInlineSnapshot('"expected function to throw an error, but it didn\'t"')
416+
}).toThrowErrorMatchingInlineSnapshot(`[AssertionError: expected function to throw an error, but it didn't]`)
417417
})
418418
})
419419
})

Diff for: ‎test/core/test/nested-test.test.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,28 @@ import { describe, expect, test } from 'vitest'
33
test('nested test should throw error', () => {
44
expect(() => {
55
test('test inside test', () => {})
6-
}).toThrowErrorMatchingInlineSnapshot(`"Nested tests are not allowed"`)
6+
}).toThrowErrorMatchingInlineSnapshot(`[Error: Nested tests are not allowed]`)
77

88
expect(() => {
99
test.each([1, 2, 3])('test.each inside test %d', () => {})
10-
}).toThrowErrorMatchingInlineSnapshot(`"Nested tests are not allowed"`)
10+
}).toThrowErrorMatchingInlineSnapshot(`[Error: Nested tests are not allowed]`)
1111

1212
expect(() => {
1313
test.skipIf(false)('test.skipIf inside test', () => {})
14-
}).toThrowErrorMatchingInlineSnapshot(`"Nested tests are not allowed"`)
14+
}).toThrowErrorMatchingInlineSnapshot(`[Error: Nested tests are not allowed]`)
1515
})
1616

1717
describe('parallel tests', () => {
1818
test.concurrent('parallel test 1 with nested test', () => {
1919
expect(() => {
2020
test('test inside test', () => {})
21-
}).toThrowErrorMatchingInlineSnapshot(`"Nested tests are not allowed"`)
21+
}).toThrowErrorMatchingInlineSnapshot(`[Error: Nested tests are not allowed]`)
2222
})
2323
test.concurrent('parallel test 2 without nested test', () => {})
2424
test.concurrent('parallel test 3 without nested test', () => {})
2525
test.concurrent('parallel test 4 with nested test', () => {
2626
expect(() => {
2727
test('test inside test', () => {})
28-
}).toThrowErrorMatchingInlineSnapshot(`"Nested tests are not allowed"`)
28+
}).toThrowErrorMatchingInlineSnapshot(`[Error: Nested tests are not allowed]`)
2929
})
3030
})

Diff for: ‎test/core/test/snapshot-custom-serializer.test.ts

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { expect, test } from 'vitest'
2+
3+
test('basic', () => {
4+
// example from docs/guide/snapshot.md
5+
6+
const bar = {
7+
foo: {
8+
x: 1,
9+
y: 2,
10+
},
11+
}
12+
13+
// without custom serializer
14+
expect(bar).toMatchInlineSnapshot(`
15+
{
16+
"foo": {
17+
"x": 1,
18+
"y": 2,
19+
},
20+
}
21+
`)
22+
23+
// with custom serializer
24+
expect.addSnapshotSerializer({
25+
serialize(val, config, indentation, depth, refs, printer) {
26+
return `Pretty foo: ${printer(
27+
val.foo,
28+
config,
29+
indentation,
30+
depth,
31+
refs,
32+
)}`
33+
},
34+
test(val) {
35+
return val && Object.prototype.hasOwnProperty.call(val, 'foo')
36+
},
37+
})
38+
39+
expect(bar).toMatchInlineSnapshot(`
40+
Pretty foo: {
41+
"x": 1,
42+
"y": 2,
43+
}
44+
`)
45+
})
46+
47+
test('throwning snapshot', () => {
48+
// example from https://github.com/vitest-dev/vitest/issues/3655
49+
50+
class ErrorWithDetails extends Error {
51+
readonly details: unknown
52+
53+
constructor(message: string, options: ErrorOptions & { details: unknown }) {
54+
super(message, options)
55+
this.details = options.details
56+
}
57+
}
58+
59+
// without custom serializer
60+
const error = new ErrorWithDetails('some-error', {
61+
details: 'some-detail',
62+
})
63+
expect(error).toMatchInlineSnapshot(`[Error: some-error]`)
64+
expect(() => {
65+
throw error
66+
}).toThrowErrorMatchingInlineSnapshot(`[Error: some-error]`)
67+
68+
// with custom serializer
69+
expect.addSnapshotSerializer({
70+
serialize(val, config, indentation, depth, refs, printer) {
71+
const error = val as ErrorWithDetails
72+
return `Pretty ${error.message}: ${printer(
73+
error.details,
74+
config,
75+
indentation,
76+
depth,
77+
refs,
78+
)}`
79+
},
80+
test(val) {
81+
return val && val instanceof ErrorWithDetails
82+
},
83+
})
84+
expect(error).toMatchInlineSnapshot(`Pretty some-error: "some-detail"`)
85+
expect(() => {
86+
throw error
87+
}).toThrowErrorMatchingInlineSnapshot(`Pretty some-error: "some-detail"`)
88+
})

Diff for: ‎test/core/test/snapshot-inline.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ test('template literal', () => {
6262
test('throwing inline snapshots', async () => {
6363
expect(() => {
6464
throw new Error('omega')
65-
}).toThrowErrorMatchingInlineSnapshot('"omega"')
65+
}).toThrowErrorMatchingInlineSnapshot(`[Error: omega]`)
6666

6767
expect(() => {
6868
// eslint-disable-next-line no-throw-literal
@@ -102,7 +102,7 @@ test('throwing inline snapshots', async () => {
102102

103103
await expect(async () => {
104104
throw new Error('omega')
105-
}).rejects.toThrowErrorMatchingInlineSnapshot('"omega"')
105+
}).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: omega]`)
106106
})
107107

108108
test('throwing expect should be a function', async () => {

Diff for: ‎test/core/test/wait.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ describe('waitFor', () => {
2424
timeout: 60,
2525
interval: 30,
2626
}),
27-
).rejects.toThrowErrorMatchingInlineSnapshot('"interval error"')
27+
).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: interval error]`)
2828

2929
expect(callback).toHaveBeenCalledTimes(2)
3030
})
@@ -125,7 +125,7 @@ describe('waitUntil', () => {
125125
timeout: 60,
126126
interval: 30,
127127
}),
128-
).rejects.toThrowErrorMatchingInlineSnapshot('"Timed out in waitUntil!"')
128+
).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Timed out in waitUntil!]`)
129129

130130
expect(callback).toHaveBeenCalledTimes(2)
131131
})

Diff for: ‎test/snapshots/test/snapshots-async.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,5 @@ test('resolved inline', async () => {
1313

1414
test('rejected inline', async () => {
1515
await expect(reject()).rejects.toMatchInlineSnapshot('[Error: foo]')
16-
await expect(reject()).rejects.toThrowErrorMatchingInlineSnapshot('"foo"')
16+
await expect(reject()).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: foo]`)
1717
})

Diff for: ‎test/utils/test/display.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ describe('format', () => {
6161
})
6262

6363
test('cannont serialize some values', () => {
64-
expect(() => format('%j', 100n)).toThrowErrorMatchingInlineSnapshot('"Do not know how to serialize a BigInt"')
64+
expect(() => format('%j', 100n)).toThrowErrorMatchingInlineSnapshot(`[TypeError: Do not know how to serialize a BigInt]`)
6565
})
6666

6767
test.each(

0 commit comments

Comments
 (0)
Please sign in to comment.