diff --git a/docs/api/mock.md b/docs/api/mock.md index 4b9e6aefb328..b1fbce45cd47 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()).toThrow('first call error') +expect(() => myMockFn()).toThrow('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 f3269ee3d637..71e3d5e8f4af 100644 --- a/test/core/test/mocking/vi-fn.test.ts +++ b/test/core/test/mocking/vi-fn.test.ts @@ -519,6 +519,88 @@ 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()).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()).toThrow('error') + expect(() => mock()).toThrow('error') + expect(() => mock()).toThrow('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()).toThrow('error') + expect(() => mock()).toThrow('error') + expect(() => mock()).toThrow('error') + mock.mockReset() + expect(mock()).toBe(undefined) + }) + + test('vi.fn() with mockThrowOnce', async () => { + const mock = vi.fn() + mock.mockThrowOnce(new Error('error')) + expect(() => mock()).toThrow('error') + expect(mock()).toBe(undefined) + expect(mock()).toBe(undefined) + mock.mockThrowOnce(new Error('error')) + mock.mockReset() + 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()).toThrow('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()).toThrow('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)