Skip to content

Commit

Permalink
feat!: spy.mockReset changes (#6426)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lordfirespeed authored Dec 5, 2024
1 parent a11d565 commit db7a888
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 28 deletions.
13 changes: 10 additions & 3 deletions docs/api/mock.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,13 @@ await asyncMock() // throws Error<'Async error'>
function mockReset(): MockInstance<T>
```

Performs the same actions as `mockClear` and sets the inner implementation to an empty function (returning `undefined` when invoked). This also resets all "once" implementations. It is useful for completely resetting a mock to its default state.
Does what `mockClear` does and resets inner implementation to the original function.
This also resets all "once" implementations.

Note that resetting a mock from `vi.fn()` will set implementation to an empty function that returns `undefined`.
resetting a mock from `vi.fn(impl)` will restore implementation to `impl`.

This is useful when you want to reset a mock to its original state.

To automatically call this method before each test, enable the [`mockReset`](/config/#mockreset) setting in the configuration.

Expand All @@ -207,9 +213,10 @@ To automatically call this method before each test, enable the [`mockReset`](/co
function mockRestore(): MockInstance<T>
```

Performs the same actions as `mockReset` and restores the inner implementation to the original function.
Does what `mockReset` does and restores original descriptors of spied-on objects.

Note that restoring a mock created with `vi.fn()` will set the implementation to an empty function that returns `undefined`. Restoring a mock created with `vi.fn(impl)` will restore the implementation to `impl`.
Note that restoring a mock from `vi.fn()` will set implementation to an empty function that returns `undefined`.
Restoring a mock from `vi.fn(impl)` will restore implementation to `impl`.

To automatically call this method before each test, enable the [`restoreMocks`](/config/#restoremocks) setting in the configuration.

Expand Down
9 changes: 6 additions & 3 deletions docs/api/vi.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,15 +398,18 @@ Checks that a given parameter is a mock function. If you are using TypeScript, i

### vi.clearAllMocks

Will call [`.mockClear()`](/api/mock#mockclear) on all spies. This will clear mock history, but not reset its implementation to the default one.
Calls [`.mockClear()`](/api/mock#mockclear) on all spies.
This will clear mock history without affecting mock implementations.

### vi.resetAllMocks

Will call [`.mockReset()`](/api/mock#mockreset) on all spies. This will clear mock history and reset its implementation to an empty function (will return `undefined`).
Calls [`.mockReset()`](/api/mock#mockreset) on all spies.
This will clear mock history and reset each mock's implementation to its original.

### vi.restoreAllMocks

Will call [`.mockRestore()`](/api/mock#mockrestore) on all spies. This will clear mock history and reset its implementation to the original one.
Calls [`.mockRestore()`](/api/mock#mockrestore) on all spies.
This will clear mock history, restore all original mock implementations, , and restore original descriptors of spied-on objects.

### vi.spyOn

Expand Down
9 changes: 6 additions & 3 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1953,21 +1953,24 @@ Custom [commands](/guide/browser/commands) that can be imported during browser t
- **Type:** `boolean`
- **Default:** `false`

Will call [`.mockClear()`](/api/mock#mockclear) on all spies before each test. This will clear mock history, but not reset its implementation to the default one.
Will call [`.mockClear()`](/api/mock#mockclear) on all spies before each test.
This will clear mock history without affecting mock implementations.

### mockReset

- **Type:** `boolean`
- **Default:** `false`

Will call [`.mockReset()`](/api/mock#mockreset) on all spies before each test. This will clear mock history and reset its implementation to an empty function (will return `undefined`).
Will call [`.mockReset()`](/api/mock#mockreset) on all spies before each test.
This will clear mock history and reset each implementation to its original.

### restoreMocks

- **Type:** `boolean`
- **Default:** `false`

Will call [`.mockRestore()`](/api/mock#mockrestore) on all spies before each test. This will clear mock history and reset its implementation to the original one.
Will call [`.mockRestore()`](/api/mock#mockrestore) on all spies before each test.
This will clear mock history, restore each implementation to its original, and restore original descriptors of spied-on objects..

### unstubEnvs {#unstubenvs}

Expand Down
8 changes: 8 additions & 0 deletions docs/guide/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,14 @@ Jest has their [globals API](https://jestjs.io/docs/api) enabled by default. Vit

If you decide to keep globals disabled, be aware that common libraries like [`testing-library`](https://testing-library.com/) will not run auto DOM [cleanup](https://testing-library.com/docs/svelte-testing-library/api/#cleanup).

### `spy.mockReset`

Jest's [`mockReset`](https://jestjs.io/docs/mock-function-api#mockfnmockreset) replaces the mock implementation with an
empty function that returns `undefined`.

Vitest's [`mockReset`](/api/mock#mockreset) resets the mock implementation to its original.
That is, resetting a mock created by `vi.fn(impl)` will reset the mock implementation to `impl`.

### Module Mocks

When mocking a module in Jest, the factory argument's return value is the default export. In Vitest, the factory argument has to return an object with each export explicitly defined. For example, the following `jest.mock` would have to be updated as follows:
Expand Down
10 changes: 6 additions & 4 deletions packages/mocker/src/automocker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,9 @@ export function mockObject(
const original = this[key]
const mock = spyOn(this, key as string)
.mockImplementation(original)
mock.mockRestore = () => {
mock.mockReset()
const origMockReset = mock.mockReset
mock.mockRestore = mock.mockReset = () => {
origMockReset.call(mock)
mock.mockImplementation(original)
return mock
}
Expand All @@ -132,8 +133,9 @@ export function mockObject(
const mock = spyOn(newContainer, property)
if (options.type === 'automock') {
mock.mockImplementation(mockFunction)
mock.mockRestore = () => {
mock.mockReset()
const origMockReset = mock.mockReset
mock.mockRestore = mock.mockReset = () => {
origMockReset.call(mock)
mock.mockImplementation(mockFunction)
return mock
}
Expand Down
10 changes: 6 additions & 4 deletions packages/spy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,17 @@ export interface MockInstance<T extends Procedure = Procedure> {
*/
mockClear(): this
/**
* Performs the same actions as `mockClear` and sets the inner implementation to an empty function (returning `undefined` when invoked). This also resets all "once" implementations. It is useful for completely resetting a mock to its default state.
* Does what `mockClear` does and resets inner implementation to the original function. This also resets all "once" implementations.
*
* Note that resetting a mock from `vi.fn()` will set implementation to an empty function that returns `undefined`.
* Resetting a mock from `vi.fn(impl)` will set implementation to `impl`. It is useful for completely resetting a mock to its default state.
*
* To automatically call this method before each test, enable the [`mockReset`](https://vitest.dev/config/#mockreset) setting in the configuration.
* @see https://vitest.dev/api/mock#mockreset
*/
mockReset(): this
/**
* Does what `mockReset` does and restores inner implementation to the original function.
* Does what `mockReset` does and restores original descriptors of spied-on objects.
*
* Note that restoring mock from `vi.fn()` will set implementation to an empty function that returns `undefined`. Restoring a `vi.fn(impl)` will restore implementation to `impl`.
* @see https://vitest.dev/api/mock#mockrestore
Expand Down Expand Up @@ -536,15 +539,14 @@ function enhanceSpy<T extends Procedure>(

stub.mockReset = () => {
stub.mockClear()
implementation = (() => undefined) as T
implementation = undefined
onceImplementations = []
return stub
}

stub.mockRestore = () => {
stub.mockReset()
state.restore()
implementation = undefined
return stub
}

Expand Down
18 changes: 13 additions & 5 deletions packages/vitest/src/integrations/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,21 +313,29 @@ export interface VitestUtils {
isMockFunction: (fn: any) => fn is MockInstance

/**
* Calls [`.mockClear()`](https://vitest.dev/api/mock#mockclear) on every mocked function. This will only empty `.mock` state, it will not reset implementation.
* Calls [`.mockClear()`](https://vitest.dev/api/mock#mockclear) on every mocked function.
*
* It is useful if you need to clean up mock between different assertions.
* This will only empty `.mock` state, it will not affect mock implementations.
*
* This is useful if you need to clean up mocks between different assertions within a test.
*/
clearAllMocks: () => VitestUtils

/**
* Calls [`.mockReset()`](https://vitest.dev/api/mock#mockreset) on every mocked function. This will empty `.mock` state, reset "once" implementations and force the base implementation to return `undefined` when invoked.
* Calls [`.mockReset()`](https://vitest.dev/api/mock#mockreset) on every mocked function.
*
* This will empty `.mock` state, reset "once" implementations, and reset each mock's base implementation to its original.
*
* This is useful when you want to completely reset a mock to the default state.
* This is useful when you want to reset all mocks to their original states.
*/
resetAllMocks: () => VitestUtils

/**
* Calls [`.mockRestore()`](https://vitest.dev/api/mock#mockrestore) on every mocked function. This will restore all original implementations.
* Calls [`.mockRestore()`](https://vitest.dev/api/mock#mockrestore) on every mocked function.
*
* This will empty `.mock` state, restore all original mock implementations, and restore original descriptors of spied-on objects.
*
* This is useful for inter-test cleanup and/or removing mocks created by [`vi.spyOn(...)`](https://vitest.dev/api/vi#vi-spyon).
*/
restoreAllMocks: () => VitestUtils

Expand Down
66 changes: 60 additions & 6 deletions test/core/test/jest-mock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ describe('jest mock compat layer', () => {
expect(a.mock.invocationCallOrder[0]).toBeLessThan(b.mock.invocationCallOrder[0])
})

it('getter spyOn', () => {
it('should spy on property getter, and mockRestore should restore original descriptor', () => {
const obj = {
get getter() {
return 'original'
Expand All @@ -244,9 +244,39 @@ describe('jest mock compat layer', () => {
spy.mockRestore()

expect(obj.getter).toBe('original')
expect(spy).not.toHaveBeenCalled()
})

it('should spy on property getter, and mockReset should not restore original descriptor', () => {
const obj = {
get getter() {
return 'original'
},
}

const spy = vi.spyOn(obj, 'getter', 'get')

expect(obj.getter).toBe('original')

spy.mockImplementation(() => 'mocked').mockImplementationOnce(() => 'once')

expect(obj.getter).toBe('once')
expect(obj.getter).toBe('mocked')
expect(obj.getter).toBe('mocked')

spy.mockReturnValue('returned').mockReturnValueOnce('returned-once')

expect(obj.getter).toBe('returned-once')
expect(obj.getter).toBe('returned')
expect(obj.getter).toBe('returned')

spy.mockReset()

expect(obj.getter).toBe('original')
expect(spy).toHaveBeenCalled()
})

it('getter function spyOn', () => {
it('should spy on function returned from property getter', () => {
const obj = {
get getter() {
return function () {
Expand All @@ -266,7 +296,7 @@ describe('jest mock compat layer', () => {
expect(obj.getter()).toBe('mocked')
})

it('setter spyOn', () => {
it('should spy on property setter (1)', () => {
let setValue = 'original'
let mockedValue = 'none'

Expand Down Expand Up @@ -309,7 +339,7 @@ describe('jest mock compat layer', () => {
expect(setValue).toBe('last')
})

it('should work - setter', () => {
it('should spy on property setter (2), and mockRestore should restore original descriptor', () => {
const obj = {
_property: false,
set property(value) {
Expand All @@ -327,12 +357,36 @@ describe('jest mock compat layer', () => {
obj.property = false
spy.mockRestore()
obj.property = true
// unlike jest, mockRestore only restores implementation to the original one,
// we are still spying on the setter
// like jest, mockRestore restores the original descriptor,
// we are not spying on the setter any more
expect(spy).not.toHaveBeenCalled()
expect(obj.property).toBe(true)
})

it('should spy on property setter (2), and mockReset should not restore original descriptor', () => {
const obj = {
_property: false,
set property(value) {
this._property = value
},
get property() {
return this._property
},
}

const spy = vi.spyOn(obj, 'property', 'set')
obj.property = true
expect(spy).toHaveBeenCalled()
expect(obj.property).toBe(true)
obj.property = false
spy.mockReset()
obj.property = true
// unlike jest, vitest's mockReset will restore original implementation without restoring the original descriptor.
// We are still spying on the setter
expect(spy).toHaveBeenCalled()
expect(obj.property).toBe(true)
})

it('throwing', async () => {
const fn = vi.fn(() => {
// eslint-disable-next-line no-throw-literal
Expand Down

0 comments on commit db7a888

Please sign in to comment.