diff --git a/docs/api/mock.md b/docs/api/mock.md index 9e051cb89fa6..9579cc03f29b 100644 --- a/docs/api/mock.md +++ b/docs/api/mock.md @@ -197,7 +197,13 @@ await asyncMock() // throws Error<'Async error'> function mockReset(): MockInstance ``` -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. @@ -207,9 +213,10 @@ To automatically call this method before each test, enable the [`mockReset`](/co function mockRestore(): MockInstance ``` -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. diff --git a/docs/api/vi.md b/docs/api/vi.md index 759ddf975c37..ee86a6327a4b 100644 --- a/docs/api/vi.md +++ b/docs/api/vi.md @@ -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 diff --git a/docs/config/index.md b/docs/config/index.md index cdf686c4ba83..ed8717bca9bf 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -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} diff --git a/docs/guide/migration.md b/docs/guide/migration.md index b0d4055eec74..50d04d751ee7 100644 --- a/docs/guide/migration.md +++ b/docs/guide/migration.md @@ -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: diff --git a/packages/mocker/src/automocker.ts b/packages/mocker/src/automocker.ts index e6b193d96899..571079e107e0 100644 --- a/packages/mocker/src/automocker.ts +++ b/packages/mocker/src/automocker.ts @@ -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 } @@ -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 } diff --git a/packages/spy/src/index.ts b/packages/spy/src/index.ts index ea8bf27084bc..2de229bb9908 100644 --- a/packages/spy/src/index.ts +++ b/packages/spy/src/index.ts @@ -198,14 +198,17 @@ export interface MockInstance { */ 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 @@ -536,7 +539,7 @@ function enhanceSpy( stub.mockReset = () => { stub.mockClear() - implementation = (() => undefined) as T + implementation = undefined onceImplementations = [] return stub } @@ -544,7 +547,6 @@ function enhanceSpy( stub.mockRestore = () => { stub.mockReset() state.restore() - implementation = undefined return stub } diff --git a/packages/vitest/src/integrations/vi.ts b/packages/vitest/src/integrations/vi.ts index 2e14b9e0f18a..402e35c24e0c 100644 --- a/packages/vitest/src/integrations/vi.ts +++ b/packages/vitest/src/integrations/vi.ts @@ -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 diff --git a/test/core/test/jest-mock.test.ts b/test/core/test/jest-mock.test.ts index bec7485e80be..a6a888c6cfd1 100644 --- a/test/core/test/jest-mock.test.ts +++ b/test/core/test/jest-mock.test.ts @@ -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' @@ -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 () { @@ -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' @@ -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) { @@ -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