From b8ea65e57801fccec904f3905dc0f41b6dc59fcc Mon Sep 17 00:00:00 2001 From: Lordfirespeed <28568841+Lordfirespeed@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:53:55 +0100 Subject: [PATCH 1/9] implement proposed changes to `spy.mockReset`, updating JSDoc --- packages/spy/src/index.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 } From b0b2d7bd93c3efd424ba7d69297e361db6e0e0ca Mon Sep 17 00:00:00 2001 From: Lordfirespeed <28568841+Lordfirespeed@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:57:17 +0100 Subject: [PATCH 2/9] update API documentation for `mockReset`, `mockRestore` --- docs/api/mock.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/api/mock.md b/docs/api/mock.md index 9e051cb89fa6..bdc3008e41c9 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 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`. + +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. From 0d4d37ad4e4f382df1a995be1d5a3ee25a01fa1f Mon Sep 17 00:00:00 2001 From: Lordfirespeed <28568841+Lordfirespeed@users.noreply.github.com> Date: Thu, 29 Aug 2024 19:59:39 +0100 Subject: [PATCH 3/9] update config documentation for `clearMocks`, `mockReset`, `restoreMocks` --- docs/config/index.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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} From 384ae2138725d35b8569fb28e1b5adde1a1c5133 Mon Sep 17 00:00:00 2001 From: Lordfirespeed <28568841+Lordfirespeed@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:04:31 +0100 Subject: [PATCH 4/9] update JSDoc for `xyzAllMocks` --- packages/vitest/src/integrations/vi.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) 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 From 7be2dd68ff482d19acba6ac423a44758272ea2f6 Mon Sep 17 00:00:00 2001 From: Lordfirespeed <28568841+Lordfirespeed@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:08:24 +0100 Subject: [PATCH 5/9] update API documentation for `vi.xyzAllMocks` --- docs/api/vi.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 From abe1570cfe04305fb3b6a23c055ba05657c3ac02 Mon Sep 17 00:00:00 2001 From: Lordfirespeed <28568841+Lordfirespeed@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:14:31 +0100 Subject: [PATCH 6/9] in the case of automocking, assign `mockReset` in addition to `mockRestore` --- packages/mocker/src/automocker.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) 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 } From e3e56d8e90a099061d38159ff238028bdb0068bd Mon Sep 17 00:00:00 2001 From: Lordfirespeed <28568841+Lordfirespeed@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:33:32 +0100 Subject: [PATCH 7/9] adjust `jest-mock` tests to test for `mockReset` API changes also improve some test names --- test/core/test/jest-mock.test.ts | 66 +++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 6 deletions(-) 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 From eaead85707b95fce3549dbd34b3a082e88a95c9e Mon Sep 17 00:00:00 2001 From: Lordfirespeed <28568841+Lordfirespeed@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:39:12 +0100 Subject: [PATCH 8/9] add `spy.mockReset` behaviour change to Jest migration guide --- docs/guide/migration.md | 8 ++++++++ 1 file changed, 8 insertions(+) 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: From c85d5d851f70f157a6c9780e0dc62a7fa7b470d8 Mon Sep 17 00:00:00 2001 From: Lordfirespeed <28568841+Lordfirespeed@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:41:36 +0100 Subject: [PATCH 9/9] fix copy paste artifact: `restoring` -> `resetting` --- 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 bdc3008e41c9..9579cc03f29b 100644 --- a/docs/api/mock.md +++ b/docs/api/mock.md @@ -200,8 +200,8 @@ function mockReset(): MockInstance Does what `mockClear` does and resets inner implementation to the original function. This also resets all "once" implementations. -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`. +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.