Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: spy.mockRevert #6378

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
18 changes: 17 additions & 1 deletion docs/api/mock.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,10 @@ If you want this method to be called before each test automatically, you can ena

Does what `mockReset` does and restores inner implementation to the original function.

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`.
Restoring a mock from `vi.spyOn(object, property)` will also restore the original descriptor of the spied-on object.

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 set implementation to `impl`.

If you want this method to be called before each test automatically, you can enable [`restoreMocks`](/config/#restoremocks) setting in config.

Expand Down Expand Up @@ -257,6 +260,19 @@ const myMockFn = vi
console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn())
```

## mockRevert

- **Type:** `() => MockInstance`

Does what `mockReset` does and reverts inner implementation to the original function.

Reverting a mock from `vi.spyOn(object, property)` will **not** restore the original descriptor of the spied-on object.

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

If you want this method to be called before each test automatically, you can enable [`revertMocks`](/config/#revertmocks) setting in config.

## mock.calls

This is an array containing all arguments for each call. One item of the array is the arguments of that call.
Expand Down
16 changes: 12 additions & 4 deletions docs/api/vi.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,15 +385,23 @@ 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 implementation to an empty function (will return `undefined`).

### 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 each implementation to its original, and restore original descriptors of spied-on objects.

### vi.revertAllMocks

Calls [`.mockRevert()`](/api/mock#mockrevert) on all spies.
This will clear mock history and revert each implementation to its original without restoring original descriptors of spied-on objects.

### vi.spyOn

Expand All @@ -417,7 +425,7 @@ expect(spy).toHaveReturnedWith(1)
```

::: tip
You can call [`vi.restoreAllMocks`](#vi-restoreallmocks) inside [`afterEach`](/api/#aftereach) (or enable [`test.restoreMocks`](/config/#restoreMocks)) to restore all methods to their original implementations. This will restore the original [object descriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), so you won't be able to change method's implementation:
You can call [`vi.restoreAllMocks`](#vi-restoreallmocks) inside [`afterEach`](/api/#aftereach) (or enable [`test.restoreMocks`](/config/#restoreMocks)) to restore all methods to its original implementations. This will restore the original [object descriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), so you won't be able to change method's implementation:

```ts
const cart = {
Expand Down
17 changes: 14 additions & 3 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1811,21 +1811,32 @@ 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 an empty function (will return `undefined`).

### 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.

### revertMocks

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

Will call [`.mockRevert()`](/api/mock#mockrevert) on all spies before each test.
This will clear mock history and revert each implementation to its original without restoring original descriptors of spied-on objects.

### unstubEnvs {#unstubenvs}

Expand Down
4 changes: 2 additions & 2 deletions packages/mocker/src/automocker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export function mockObject(
const original = this[key]
const mock = spyOn(this, key as string)
.mockImplementation(original)
mock.mockRestore = () => {
mock.mockRevert = mock.mockRestore = () => {
mock.mockReset()
mock.mockImplementation(original)
return mock
Expand All @@ -132,7 +132,7 @@ export function mockObject(
const mock = spyOn(newContainer, property)
if (options.type === 'automock') {
mock.mockImplementation(mockFunction)
mock.mockRestore = () => {
mock.mockRevert = mock.mockRestore = () => {
mock.mockReset()
mock.mockImplementation(mockFunction)
return mock
Expand Down
41 changes: 39 additions & 2 deletions packages/spy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,20 +185,51 @@ export interface MockInstance<T extends Procedure = Procedure> {
* Clears all information about every call. After calling it, all properties on `.mock` will return an empty state. This method does not reset implementations.
*
* It is useful if you need to clean up mock between different assertions.
*
* @see mockReset
* @see mockRestore
* @see mockRevert
*/
mockClear(): this
/**
* Does what `mockClear` does and makes inner implementation an empty function (returning `undefined` when invoked). This also resets all "once" implementations.
* Does what `mockClear` does and makes inner implementation an empty function (returning `undefined` when invoked).
* This also resets all "once" implementations.
*
* This is useful when you want to completely reset a mock to the default state.
*
* @see mockClear
* @see mockRestore
* @see mockRevert
*/
mockReset(): this
/**
* Does what `mockReset` does and restores inner implementation to the original function.
*
* 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`.
* Restoring a mock from `vi.spyOn(object, property)` will also restore the original
* descriptor of the spied-on object.
*
* Note that restoring mock from `vi.fn()` will set implementation to an empty function that returns `undefined`.
* Restoring a mock from `vi.fn(impl)` will set implementation to `impl`.
*
* @see mockClear
* @see mockReset
* @see mockRevert
*/
mockRestore(): void
/**
* Does what `mockReset` does and reverts inner implementation to the original function.
*
* Reverting a mock from `vi.spyOn(object, property)` will **not** restore the original
* descriptor of the spied-on object.
*
* Note that reverting a mock from `vi.fn()` will set implementation to an empty function that returns `undefined`.
* Reverting a mock from `vi.fn(impl)` will set implementation to `impl`.
*
* @see mockClear
* @see mockReset
* @see mockRestore
*/
mockRevert(): void
/**
* Returns current mock implementation if there is one.
*
Expand Down Expand Up @@ -520,6 +551,12 @@ function enhanceSpy<T extends Procedure>(
return stub
}

stub.mockRevert = () => {
stub.mockReset()
implementation = undefined
return stub
}

stub.getMockImplementation = () => implementation
stub.mockImplementation = (fn: T) => {
implementation = fn
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ const config = {
pool: 'forks' as const,
clearMocks: false,
restoreMocks: false,
revertMocks: false,
mockReset: false,
unstubGlobals: false,
unstubEnvs: false,
Expand Down
32 changes: 27 additions & 5 deletions packages/vitest/src/integrations/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,24 +315,41 @@ 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 force the base implementation to return `undefined` when invoked.
*
* This is useful when you want to completely reset a mock to the default state.
* This is useful when you want to completely reset mocks to the default state.
*/
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

/**
* Calls [`.mockRevert()`](https://vitest.dev/api/mock#mockrevert) on every mocked function.
*
* This will empty `.mock` state and revert all mocks to their original implementations without restoring original descriptors of spied-on objects.
*
* This is useful for inter-test cleanup without removing mocks created by [`vi.spyOn(...)`](https://vitest.dev/api/vi#vi-spyon).
*/
revertAllMocks: () => VitestUtils

/**
* Makes value available on global namespace.
* Useful, if you want to have global variables available, like `IntersectionObserver`.
Expand Down Expand Up @@ -655,6 +672,11 @@ function createVitest(): VitestUtils {
return utils
},

revertAllMocks() {
mocks.forEach(spy => spy.mockRevert())
return utils
},

stubGlobal(name: string | symbol | number, value: any) {
if (!_stubsGlobal.has(name)) {
_stubsGlobal.set(
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/cli/cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,7 @@ export const cliOptionsConfig: VitestCLIOptions = {
unstubEnvs: null,
related: null,
restoreMocks: null,
revertMocks: null,
runner: null,
mockReset: null,
forceRerunTriggers: null,
Expand Down
1 change: 1 addition & 0 deletions packages/vitest/src/node/config/serializeConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function serializeConfig(
clearMocks: config.clearMocks,
mockReset: config.mockReset,
restoreMocks: config.restoreMocks,
revertMocks: config.revertMocks,
unstubEnvs: config.unstubEnvs,
unstubGlobals: config.unstubGlobals,
maxConcurrency: config.maxConcurrency,
Expand Down
6 changes: 6 additions & 0 deletions packages/vitest/src/node/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,12 @@ export interface InlineConfig {
*/
restoreMocks?: boolean

/**
* Will call `.mockRevert()` on all spies before each test
* @default false
*/
revertMocks?: boolean

/**
* Will restore all global stubs to their original values before each test
* @default false
Expand Down
2 changes: 2 additions & 0 deletions packages/vitest/src/runtime/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface SerializedConfig {
clearMocks: boolean
mockReset: boolean
restoreMocks: boolean
revertMocks: boolean
unstubGlobals: boolean
unstubEnvs: boolean
// TODO: make optional
Expand Down Expand Up @@ -149,6 +150,7 @@ export type RuntimeConfig = Pick<
| 'clearMocks'
| 'mockReset'
| 'restoreMocks'
| 'revertMocks'
| 'fakeTimers'
| 'maxConcurrency'
| 'expect'
Expand Down
5 changes: 4 additions & 1 deletion packages/vitest/src/runtime/runners/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,16 @@ export class VitestTestRunner implements VitestRunner {
}

function clearModuleMocks(config: SerializedConfig) {
const { clearMocks, mockReset, restoreMocks, unstubEnvs, unstubGlobals }
const { clearMocks, mockReset, restoreMocks, revertMocks, unstubEnvs, unstubGlobals }
= config

// since each function calls another, we can just call one
if (restoreMocks) {
vi.restoreAllMocks()
}
else if (revertMocks) {
vi.revertAllMocks()
}
else if (mockReset) {
vi.resetAllMocks()
}
Expand Down
Loading