Skip to content

Commit 619179f

Browse files
feat: implement mockThrow and mockThrowOnce (#9512)
Co-authored-by: Vladimir Sheremet <sleuths.slews0s@icloud.com>
1 parent 7ef5cf4 commit 619179f

File tree

4 files changed

+152
-0
lines changed

4 files changed

+152
-0
lines changed

docs/api/mock.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,40 @@ const myMockFn = vi
418418
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn())
419419
```
420420

421+
## mockThrow <Version>4.1.0</Version> {#mockthrow}
422+
423+
```ts
424+
function mockThrow(value: unknown): Mock<T>
425+
```
426+
427+
Accepts a value that will be thrown whenever the mock function is called.
428+
429+
```ts
430+
const myMockFn = vi.fn()
431+
myMockFn.mockThrow(new Error('error message'))
432+
myMockFn() // throws Error<'error message'>
433+
```
434+
435+
## mockThrowOnce <Version>4.1.0</Version> {#mockthrowonce}
436+
437+
```ts
438+
function mockThrowOnce(value: unknown): Mock<T>
439+
```
440+
441+
Accepts a value that will be thrown during the next function call. If chained, every consecutive call will throw the specified value.
442+
443+
```ts
444+
const myMockFn = vi
445+
.fn()
446+
.mockReturnValue('default')
447+
.mockThrowOnce(new Error('first call error'))
448+
.mockThrowOnce('second call error')
449+
450+
expect(() => myMockFn()).toThrow('first call error')
451+
expect(() => myMockFn()).toThrow('second call error')
452+
expect(myMockFn()).toEqual('default')
453+
```
454+
421455
## mock.calls
422456

423457
```ts

packages/spy/src/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,20 @@ export function createMockInstance(options: MockInstanceOption = {}): Mock<Proce
140140
})
141141
}
142142

143+
mock.mockThrow = function mockThrow(value) {
144+
// eslint-disable-next-line prefer-arrow-callback
145+
return mock.mockImplementation(function () {
146+
throw value
147+
})
148+
}
149+
150+
mock.mockThrowOnce = function mockThrowOnce(value) {
151+
// eslint-disable-next-line prefer-arrow-callback
152+
return mock.mockImplementationOnce(function () {
153+
throw value
154+
})
155+
}
156+
143157
mock.mockResolvedValue = function mockResolvedValue(value) {
144158
return mock.mockImplementation(function () {
145159
if (new.target) {

packages/spy/src/types.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,28 @@ export interface MockInstance<T extends Procedure | Constructable = Procedure> e
318318
* console.log(myMockFn(), myMockFn(), myMockFn())
319319
*/
320320
mockReturnValueOnce(value: MockReturnType<T>): this
321+
/**
322+
* Accepts a value that will be thrown whenever the mock function is called.
323+
* @see https://vitest.dev/api/mock#mockthrow
324+
* @example
325+
* const myMockFn = vi.fn().mockThrow(new Error('error'))
326+
* myMockFn() // throws 'error'
327+
*/
328+
mockThrow(value: unknown): this
329+
/**
330+
* Accepts a value that will be thrown during the next function call. If chained, every consecutive call will throw the specified value.
331+
* @example
332+
* const myMockFn = vi
333+
* .fn()
334+
* .mockReturnValue('default')
335+
* .mockThrowOnce(new Error('first call error'))
336+
* .mockThrowOnce('second call error')
337+
*
338+
* expect(() => myMockFn()).toThrowError('first call error')
339+
* expect(() => myMockFn()).toThrowError('second call error')
340+
* expect(myMockFn()).toEqual('default')
341+
*/
342+
mockThrowOnce(value: unknown): this
321343
/**
322344
* Accepts a value that will be resolved when the async function is called. TypeScript will only accept values that match the return type of the original function.
323345
* @example

test/core/test/mocking/vi-fn.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,88 @@ describe('vi.fn() implementations', () => {
519519
expect(mock()).toBe(undefined)
520520
})
521521

522+
test('vi.fn() with mockThrow', async () => {
523+
const mock = vi.fn()
524+
mock.mockThrow(new Error('error'))
525+
expect(() => mock()).toThrow('error')
526+
expect(() => mock()).toThrow('error')
527+
expect(() => mock()).toThrow('error')
528+
mock.mockReset()
529+
expect(mock()).toBe(undefined)
530+
})
531+
532+
test('vi.fn(class) with mockThrow', async () => {
533+
const Mock = vi.fn(class {})
534+
Mock.mockThrow(new Error('error'))
535+
expect(() => new Mock()).toThrow('error')
536+
expect(() => new Mock()).toThrow('error')
537+
expect(() => new Mock()).toThrow('error')
538+
Mock.mockReset()
539+
expect(new Mock()).toBeInstanceOf(Mock)
540+
})
541+
542+
test('vi.fn() with mockThrow overriding original mock', async () => {
543+
const mock = vi.fn(() => 42)
544+
mock.mockThrow(new Error('error'))
545+
expect(() => mock()).toThrow('error')
546+
expect(() => mock()).toThrow('error')
547+
expect(() => mock()).toThrow('error')
548+
mock.mockReset()
549+
expect(mock()).toBe(42)
550+
})
551+
552+
test('vi.fn() with mockThrow overriding another mock', async () => {
553+
const mock = vi.fn().mockImplementation(() => 42)
554+
mock.mockThrow(new Error('error'))
555+
expect(() => mock()).toThrow('error')
556+
expect(() => mock()).toThrow('error')
557+
expect(() => mock()).toThrow('error')
558+
mock.mockReset()
559+
expect(mock()).toBe(undefined)
560+
})
561+
562+
test('vi.fn() with mockThrowOnce', async () => {
563+
const mock = vi.fn()
564+
mock.mockThrowOnce(new Error('error'))
565+
expect(() => mock()).toThrow('error')
566+
expect(mock()).toBe(undefined)
567+
expect(mock()).toBe(undefined)
568+
mock.mockThrowOnce(new Error('error'))
569+
mock.mockReset()
570+
expect(mock()).toBe(undefined)
571+
})
572+
573+
test('vi.fn(class) with mockThrowOnce', async () => {
574+
const Mock = vi.fn(class {})
575+
Mock.mockThrowOnce(new Error('error'))
576+
expect(() => new Mock()).toThrow('error')
577+
expect(new Mock()).toBeInstanceOf(Mock)
578+
expect(new Mock()).toBeInstanceOf(Mock)
579+
Mock.mockThrowOnce(new Error('error'))
580+
Mock.mockReset()
581+
expect(new Mock()).toBeInstanceOf(Mock)
582+
})
583+
584+
test('vi.fn() with mockThrowOnce overriding original mock', async () => {
585+
const mock = vi.fn(() => 42)
586+
mock.mockThrowOnce(new Error('error'))
587+
expect(() => mock()).toThrow('error')
588+
expect(mock()).toBe(42)
589+
expect(mock()).toBe(42)
590+
mock.mockReset()
591+
expect(mock()).toBe(42)
592+
})
593+
594+
test('vi.fn() with mockThrowOnce overriding another mock', async () => {
595+
const mock = vi.fn().mockImplementation(() => 42)
596+
mock.mockThrowOnce(new Error('error'))
597+
expect(() => mock()).toThrow('error')
598+
expect(mock()).toBe(42)
599+
expect(mock()).toBe(42)
600+
mock.mockReset()
601+
expect(mock()).toBe(undefined)
602+
})
603+
522604
test('vi.fn() with mockResolvedValue', async () => {
523605
const mock = vi.fn()
524606
mock.mockResolvedValue(42)

0 commit comments

Comments
 (0)