From 7e66b8b196a971128dcec3b92d6ca2a0724986ed Mon Sep 17 00:00:00 2001 From: Thor Juhasz Date: Fri, 23 Jan 2026 14:57:59 +0100 Subject: [PATCH 1/3] feat: implement `mockThrow` and `mockThrowOnce` --- docs/api/mock.md | 34 ++++++++++++++++ packages/spy/src/index.ts | 14 +++++++ packages/spy/src/types.ts | 22 ++++++++++ test/core/test/mocking/vi-fn.test.ts | 61 ++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) diff --git a/docs/api/mock.md b/docs/api/mock.md index 860e2c0d49e4..534ab765d049 100644 --- a/docs/api/mock.md +++ b/docs/api/mock.md @@ -418,6 +418,40 @@ const myMockFn = vi console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn()) ``` +## mockThrow 4.1.0 {#mockthrow} + +```ts +function mockThrow(value: unknown): Mock +``` + +Accepts a value that will be thrown whenever the mock function is called. + +```ts +const myMockFn = vi.fn() +myMockFn.mockThrow(new Error('error message')) +myMockFn() // throws Error<'error message'> +``` + +## mockThrowOnce 4.1.0 {#mockthrowonce} + +```ts +function mockThrowOnce(value: unknown): Mock +``` + +Accepts a value that will be thrown during the next function call. If chained, every consecutive call will throw the specified value. + +```ts +const myMockFn = vi + .fn() + .mockReturnValue('default') + .mockThrowOnce(new Error('first call error')) + .mockThrowOnce('second call error') + +expect(() => myMockFn()).toThrowError('first call error') +expect(() => myMockFn()).toThrowError('second call error') +expect(myMockFn()).toEqual('default') +``` + ## mock.calls ```ts diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index 0b20accc9803..80a1e48bd6b0 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -140,6 +140,20 @@ export function createMockInstance(options: MockInstanceOption = {}): Mock e * console.log(myMockFn(), myMockFn(), myMockFn()) */ mockReturnValueOnce(value: MockReturnType): this + /** + * Accepts a value that will be thrown whenever the mock function is called. + * @see https://vitest.dev/api/mock#mockthrow + * @example + * const myMockFn = vi.fn().mockThrow(new Error('error')) + * myMockFn() // throws 'error' + */ + mockThrow(value: unknown): this + /** + * Accepts a value that will be thrown during the next function call. If chained, every consecutive call will throw the specified value. + * @example + * const myMockFn = vi + * .fn() + * .mockReturnValue('default') + * .mockThrowOnce(new Error('first call error')) + * .mockThrowOnce('second call error') + * + * expect(() => myMockFn()).toThrowError('first call error') + * expect(() => myMockFn()).toThrowError('second call error') + * expect(myMockFn()).toEqual('default') + */ + mockThrowOnce(value: unknown): this /** * 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. * @example diff --git a/test/core/test/mocking/vi-fn.test.ts b/test/core/test/mocking/vi-fn.test.ts index 5505c1b7014a..1059bfb9eb1a 100644 --- a/test/core/test/mocking/vi-fn.test.ts +++ b/test/core/test/mocking/vi-fn.test.ts @@ -519,6 +519,67 @@ describe('vi.fn() implementations', () => { expect(mock()).toBe(undefined) }) + test('vi.fn() with mockThrow', async () => { + const mock = vi.fn() + mock.mockThrow(new Error('error')) + expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrowError('error') + mock.mockReset() + expect(mock()).toBe(undefined) + }) + + test('vi.fn() with mockThrow overriding original mock', async () => { + const mock = vi.fn(() => 42) + mock.mockThrow(new Error('error')) + expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrowError('error') + mock.mockReset() + expect(mock()).toBe(42) + }) + + test('vi.fn() with mockThrow overriding another mock', async () => { + const mock = vi.fn().mockImplementation(() => 42) + mock.mockThrow(new Error('error')) + expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrowError('error') + mock.mockReset() + expect(mock()).toBe(undefined) + }) + + test('vi.fn() with mockThrowOnce', async () => { + const mock = vi.fn() + mock.mockThrowOnce(new Error('error')) + expect(() => mock()).toThrowError('error') + expect(mock()).toBe(undefined) + expect(mock()).toBe(undefined) + mock.mockThrowOnce(new Error('error')) + mock.mockReset() + expect(mock()).toBe(undefined) + }) + + test('vi.fn() with mockThrowOnce overriding original mock', async () => { + const mock = vi.fn(() => 42) + mock.mockThrowOnce(new Error('error')) + expect(() => mock()).toThrowError('error') + expect(mock()).toBe(42) + expect(mock()).toBe(42) + mock.mockReset() + expect(mock()).toBe(42) + }) + + test('vi.fn() with mockThrowOnce overriding another mock', async () => { + const mock = vi.fn().mockImplementation(() => 42) + mock.mockThrowOnce(new Error('error')) + expect(() => mock()).toThrowError('error') + expect(mock()).toBe(42) + expect(mock()).toBe(42) + mock.mockReset() + expect(mock()).toBe(undefined) + }) + test('vi.fn() with mockResolvedValue', async () => { const mock = vi.fn() mock.mockResolvedValue(42) From edf76a3d9b918b53b71c38776d516eb71f885512 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 16 Feb 2026 16:35:30 +0100 Subject: [PATCH 2/3] test: add class.mockThrow tests --- test/core/test/mocking/vi-fn.test.ts | 45 ++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/test/core/test/mocking/vi-fn.test.ts b/test/core/test/mocking/vi-fn.test.ts index 7d345ce10223..71e3d5e8f4af 100644 --- a/test/core/test/mocking/vi-fn.test.ts +++ b/test/core/test/mocking/vi-fn.test.ts @@ -522,19 +522,29 @@ describe('vi.fn() implementations', () => { test('vi.fn() with mockThrow', async () => { const mock = vi.fn() mock.mockThrow(new Error('error')) - expect(() => mock()).toThrowError('error') - expect(() => mock()).toThrowError('error') - expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrow('error') + expect(() => mock()).toThrow('error') + expect(() => mock()).toThrow('error') mock.mockReset() expect(mock()).toBe(undefined) }) + test('vi.fn(class) with mockThrow', async () => { + const Mock = vi.fn(class {}) + Mock.mockThrow(new Error('error')) + expect(() => new Mock()).toThrow('error') + expect(() => new Mock()).toThrow('error') + expect(() => new Mock()).toThrow('error') + Mock.mockReset() + expect(new Mock()).toBeInstanceOf(Mock) + }) + test('vi.fn() with mockThrow overriding original mock', async () => { const mock = vi.fn(() => 42) mock.mockThrow(new Error('error')) - expect(() => mock()).toThrowError('error') - expect(() => mock()).toThrowError('error') - expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrow('error') + expect(() => mock()).toThrow('error') + expect(() => mock()).toThrow('error') mock.mockReset() expect(mock()).toBe(42) }) @@ -542,9 +552,9 @@ describe('vi.fn() implementations', () => { test('vi.fn() with mockThrow overriding another mock', async () => { const mock = vi.fn().mockImplementation(() => 42) mock.mockThrow(new Error('error')) - expect(() => mock()).toThrowError('error') - expect(() => mock()).toThrowError('error') - expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrow('error') + expect(() => mock()).toThrow('error') + expect(() => mock()).toThrow('error') mock.mockReset() expect(mock()).toBe(undefined) }) @@ -552,7 +562,7 @@ describe('vi.fn() implementations', () => { test('vi.fn() with mockThrowOnce', async () => { const mock = vi.fn() mock.mockThrowOnce(new Error('error')) - expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrow('error') expect(mock()).toBe(undefined) expect(mock()).toBe(undefined) mock.mockThrowOnce(new Error('error')) @@ -560,10 +570,21 @@ describe('vi.fn() implementations', () => { expect(mock()).toBe(undefined) }) + test('vi.fn(class) with mockThrowOnce', async () => { + const Mock = vi.fn(class {}) + Mock.mockThrowOnce(new Error('error')) + expect(() => new Mock()).toThrow('error') + expect(new Mock()).toBeInstanceOf(Mock) + expect(new Mock()).toBeInstanceOf(Mock) + Mock.mockThrowOnce(new Error('error')) + Mock.mockReset() + expect(new Mock()).toBeInstanceOf(Mock) + }) + test('vi.fn() with mockThrowOnce overriding original mock', async () => { const mock = vi.fn(() => 42) mock.mockThrowOnce(new Error('error')) - expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrow('error') expect(mock()).toBe(42) expect(mock()).toBe(42) mock.mockReset() @@ -573,7 +594,7 @@ describe('vi.fn() implementations', () => { test('vi.fn() with mockThrowOnce overriding another mock', async () => { const mock = vi.fn().mockImplementation(() => 42) mock.mockThrowOnce(new Error('error')) - expect(() => mock()).toThrowError('error') + expect(() => mock()).toThrow('error') expect(mock()).toBe(42) expect(mock()).toBe(42) mock.mockReset() From 490e055911953086b5d69e21e4e8f52330a09052 Mon Sep 17 00:00:00 2001 From: Vladimir Sheremet Date: Mon, 16 Feb 2026 16:44:21 +0100 Subject: [PATCH 3/3] chore: toThrowError -> toThrow --- docs/api/mock.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api/mock.md b/docs/api/mock.md index 8f912a24e10f..b1fbce45cd47 100644 --- a/docs/api/mock.md +++ b/docs/api/mock.md @@ -447,8 +447,8 @@ const myMockFn = vi .mockThrowOnce(new Error('first call error')) .mockThrowOnce('second call error') -expect(() => myMockFn()).toThrowError('first call error') -expect(() => myMockFn()).toThrowError('second call error') +expect(() => myMockFn()).toThrow('first call error') +expect(() => myMockFn()).toThrow('second call error') expect(myMockFn()).toEqual('default') ```