Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions docs/api/mock.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,40 @@ const myMockFn = vi
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn())
```

## mockThrow <Version>4.1.0</Version> {#mockthrow}

```ts
function mockThrow(value: unknown): Mock<T>
```

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 <Version>4.1.0</Version> {#mockthrowonce}

```ts
function mockThrowOnce(value: unknown): Mock<T>
```

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
Expand Down
14 changes: 14 additions & 0 deletions packages/spy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ export function createMockInstance(options: MockInstanceOption = {}): Mock<Proce
})
}

mock.mockThrow = function mockThrow(value) {
// eslint-disable-next-line prefer-arrow-callback
return mock.mockImplementation(function () {
throw value
})
}

mock.mockThrowOnce = function mockThrowOnce(value) {
// eslint-disable-next-line prefer-arrow-callback
return mock.mockImplementationOnce(function () {
throw value
})
}

mock.mockResolvedValue = function mockResolvedValue(value) {
return mock.mockImplementation(function () {
if (new.target) {
Expand Down
22 changes: 22 additions & 0 deletions packages/spy/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,28 @@ export interface MockInstance<T extends Procedure | Constructable = Procedure> e
* console.log(myMockFn(), myMockFn(), myMockFn())
*/
mockReturnValueOnce(value: MockReturnType<T>): 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
Expand Down
82 changes: 82 additions & 0 deletions test/core/test/mocking/vi-fn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a test with the constructor I mentioned in #9512 (review) ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am unsure what you mean here...
The mockReturn and mockReturnOnce test cases do not cover any such thing, they just return whatever you pass.
Likewise, mockThrow and mockThrowOnce just throw whatever you pass.

I did change them to use function () { .. } instead of () => { .. } as you requested, but fail to understand what you want tested here.

Copy link
Copy Markdown
Member

@sheremet-va sheremet-va Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am asking you to mock a class that throws an error vi.fn(class {}) in the constructor (vi.fn(class {}).mockThrow())

Others don’t have it because they don’t support classes

test('vi.fn() with mockResolvedValue', async () => {
const mock = vi.fn()
mock.mockResolvedValue(42)
Expand Down
Loading