Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
716e207
fix!: rewrite spying implementation to make module mocking more logical
sheremet-va Jul 30, 2025
2082369
chore: lint
sheremet-va Jul 30, 2025
f657700
fix: copy static props, restore some previous behaviours
sheremet-va Jul 30, 2025
d3f87e4
fix: do not copy getters
sheremet-va Jul 30, 2025
bfda423
fix: keep getters and setters in spy mode
sheremet-va Jul 30, 2025
86eb9b1
chore: lint
sheremet-va Jul 30, 2025
6a1ba96
fix: keep using the original name in vi.spyOn
sheremet-va Jul 30, 2025
b3f3e22
docs: add migration guide
sheremet-va Jul 30, 2025
3cc251b
chore: remove debugging comment
sheremet-va Jul 30, 2025
fde9888
test: fix browser snapshot test
sheremet-va Jul 30, 2025
9bae554
fix: don't allow overriding mock.mock
sheremet-va Jul 30, 2025
f7d0227
docs: add example of mock.mock to jest differences
sheremet-va Jul 30, 2025
97c01d8
chore: remove tinyspy from dependencies
sheremet-va Jul 30, 2025
bec02bf
refactor: cleanup
sheremet-va Jul 31, 2025
5f12b6a
docs: cleanup
sheremet-va Jul 31, 2025
0e14a62
test: more test
sheremet-va Jul 31, 2025
2a03266
docs: add mocking modules guide
sheremet-va Jul 31, 2025
b6932df
fix: don't empty array if `spy` is set to `true`
sheremet-va Jul 31, 2025
46b4846
chore: fix links
sheremet-va Jul 31, 2025
93cb84c
docs: cleanup
sheremet-va Jul 31, 2025
c1e2252
docs: cleanup
sheremet-va Jul 31, 2025
1cb2d0d
chore: add a log for console.warn
sheremet-va Aug 1, 2025
4bb8b5e
docs: add virtual modules
sheremet-va Aug 1, 2025
35c3aef
docs: update mocking docs
sheremet-va Aug 1, 2025
6c2a4d5
test: don't use using
sheremet-va Aug 1, 2025
952755d
docs: remove irrelevant comment
sheremet-va Aug 1, 2025
72a0634
docs: fix link
sheremet-va Aug 1, 2025
5a32529
docs: cleanup
sheremet-va Aug 1, 2025
34adde5
chore: cleanup
sheremet-va Aug 1, 2025
4f48ca9
Merge branch 'main' of github.com:vitest-dev/vitest into fix/mocking-…
sheremet-va Aug 1, 2025
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
35 changes: 35 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,41 @@ function guide(): DefaultTheme.SidebarItem[] {
{
text: 'Mocking',
link: '/guide/mocking',
collapsed: true,
items: [
{
text: 'Mocking Dates',
link: '/guide/mocking#dates',
},
{
text: 'Mocking Functions',
link: '/guide/mocking#functions',
},
{
text: 'Mocking Globals',
link: '/guide/mocking#globals',
},
{
text: 'Mocking Modules',
link: '/guide/mocking-modules',
},
{
text: 'Mocking File System',
link: '/guide/mocking#file-system',
},
{
text: 'Mocking Requests',
link: '/guide/mocking#requests',
},
{
text: 'Mocking Timers',
link: '/guide/mocking#timers',
},
{
text: 'Mocking Classes',
link: '/guide/mocking#classes',
},
],
},
{
text: 'Parallelism',
Expand Down
43 changes: 27 additions & 16 deletions docs/api/mock.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Use it to return the name assigned to the mock with the `.mockName(name)` method
## mockClear

```ts
function mockClear(): MockInstance<T>
function mockClear(): Mock<T>
```

Clears all information about every call. After calling it, all properties on `.mock` will return to their initial state. This method does not reset implementations. It is useful for cleaning up mocks between different assertions.
Expand All @@ -72,15 +72,15 @@ To automatically call this method before each test, enable the [`clearMocks`](/c
## mockName

```ts
function mockName(name: string): MockInstance<T>
function mockName(name: string): Mock<T>
```

Sets the internal mock name. This is useful for identifying the mock when an assertion fails.

## mockImplementation

```ts
function mockImplementation(fn: T): MockInstance<T>
function mockImplementation(fn: T): Mock<T>
```

Accepts a function to be used as the mock implementation. TypeScript expects the arguments and return type to match those of the original function.
Expand All @@ -102,7 +102,7 @@ mockFn.mock.calls[1][0] === 1 // true
## mockImplementationOnce

```ts
function mockImplementationOnce(fn: T): MockInstance<T>
function mockImplementationOnce(fn: T): Mock<T>
```

Accepts a function to be used as the mock implementation. TypeScript expects the arguments and return type to match those of the original function. This method can be chained to produce different results for multiple function calls.
Expand Down Expand Up @@ -135,11 +135,11 @@ console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn())
function withImplementation(
fn: T,
cb: () => void
): MockInstance<T>
): Mock<T>
function withImplementation(
fn: T,
cb: () => Promise<void>
): Promise<MockInstance<T>>
): Promise<Mock<T>>
```

Overrides the original mock implementation temporarily while the callback is being executed.
Expand Down Expand Up @@ -177,7 +177,7 @@ Note that this method takes precedence over the [`mockImplementationOnce`](#mock
## mockRejectedValue

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

Accepts an error that will be rejected when async function is called.
Expand All @@ -191,7 +191,7 @@ await asyncMock() // throws Error<'Async error'>
## mockRejectedValueOnce

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

Accepts a value that will be rejected during the next function call. If chained, each consecutive call will reject the specified value.
Expand All @@ -209,7 +209,7 @@ await asyncMock() // throws Error<'Async error'>
## mockReset

```ts
function mockReset(): MockInstance<T>
function mockReset(): Mock<T>
```

Does what [`mockClear`](#mockClear) does and resets inner implementation to the original function.
Expand Down Expand Up @@ -241,7 +241,7 @@ To automatically call this method before each test, enable the [`mockReset`](/co
## mockRestore

```ts
function mockRestore(): MockInstance<T>
function mockRestore(): Mock<T>
```

Does what [`mockReset`](#mockReset) does and restores original descriptors of spied-on objects.
Expand Down Expand Up @@ -270,7 +270,7 @@ To automatically call this method before each test, enable the [`restoreMocks`](
## mockResolvedValue

```ts
function mockResolvedValue(value: Awaited<ReturnType<T>>): MockInstance<T>
function mockResolvedValue(value: Awaited<ReturnType<T>>): Mock<T>
```

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.
Expand All @@ -284,7 +284,7 @@ await asyncMock() // 42
## mockResolvedValueOnce

```ts
function mockResolvedValueOnce(value: Awaited<ReturnType<T>>): MockInstance<T>
function mockResolvedValueOnce(value: Awaited<ReturnType<T>>): Mock<T>
```

Accepts a value that will be resolved during the next function call. TypeScript will only accept values that match the return type of the original function. If chained, each consecutive call will resolve the specified value.
Expand All @@ -305,7 +305,7 @@ await asyncMock() // default
## mockReturnThis

```ts
function mockReturnThis(): MockInstance<T>
function mockReturnThis(): Mock<T>
```

Use this if you need to return the `this` context from the method without invoking the actual implementation. This is a shorthand for:
Expand All @@ -319,7 +319,7 @@ spy.mockImplementation(function () {
## mockReturnValue

```ts
function mockReturnValue(value: ReturnType<T>): MockInstance<T>
function mockReturnValue(value: ReturnType<T>): Mock<T>
```

Accepts a value that will be returned whenever the mock function is called. TypeScript will only accept values that match the return type of the original function.
Expand All @@ -335,7 +335,7 @@ mock() // 43
## mockReturnValueOnce

```ts
function mockReturnValueOnce(value: ReturnType<T>): MockInstance<T>
function mockReturnValueOnce(value: ReturnType<T>): Mock<T>
```

Accepts a value that will be returned whenever the mock function is called. TypeScript will only accept values that match the return type of the original function.
Expand Down Expand Up @@ -450,6 +450,11 @@ fn.mock.results === [
## mock.settledResults

```ts
interface MockSettledResultIncomplete {
type: 'incomplete'
value: undefined
}

interface MockSettledResultFulfilled<T> {
type: 'fulfilled'
value: T
Expand All @@ -463,6 +468,7 @@ interface MockSettledResultRejected {
export type MockSettledResult<T>
= | MockSettledResultFulfilled<T>
| MockSettledResultRejected
| MockSettledResultIncomplete

const settledResults: MockSettledResult<Awaited<ReturnType<T>>>[]
```
Expand All @@ -476,7 +482,12 @@ const fn = vi.fn().mockResolvedValueOnce('result')

const result = fn()

fn.mock.settledResults === []
fn.mock.settledResults === [
{
type: 'incomplete',
value: undefined,
},
]

await result

Expand Down
47 changes: 46 additions & 1 deletion docs/guide/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,39 @@ const mock = new Spy()

Note that now if you provide an arrow function, you will get [`<anonymous> is not a constructor` error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Not_a_constructor) when the mock is called.

### Changes to Mocking

Alongside new features like supporting constructors, Vitest 4 creates mocks differently to address several module mocking issues that we received over the years. This release attemts to make module spies less confusing, especially when working with classes.
Comment thread
sheremet-va marked this conversation as resolved.

- `vi.fn().getMockName()` now returns `vi.fn()` by default instead of `spy`. This can affect snapshots with mocks - the name will be changed from `[MockFunction spy]` to `[MockFunction]`. Spies created with `vi.spyOn` will keep using the original name by default for better debugging experience
- `vi.restoreAllMocks` no longer resets the state of spies and only restores spies created manually with `vi.spyOn`, automocks are no longer affected by this function (this also affects the config option [`restoreMocks`](/config/#restoremocks)). Note that `.mockRestore` will still reset the mock implementation and clear the state
- Calling `vi.spyOn` on a mock now returns the same mock
- Automocked instance methods are now properly isolated, but share a state with the prototype. Overriding the prototype implementation will always affect instance methods unless the methods have a custom mock implementation of their own. Calling `.mockReset` on the mock also no longer breaks that inheritance.
```ts
import { AutoMockedClass } from './example.js'
const instance1 = new AutoMockedClass()
const instance2 = new AutoMockedClass()

instance1.method.mockReturnValue(42)

expect(instance1.method()).toBe(42)
expect(instance2.method()).toBe(undefined)

expect(AutoMockedClass.prototype.method).toHaveBeenCalledTimes(2)

instance1.method.mockReset()
AutoMockedClass.prototype.method.mockReturnValue(100)

expect(instance1.method()).toBe(100)
expect(instance2.method()).toBe(100)

expect(AutoMockedClass.prototype.method).toHaveBeenCalledTimes(4)
```
- Automocked methods can no longer be restored, even with a manual `.mockRestore`. Automocked modules with `spy: true` will keep working as before
- Automocked getters no longer call the original getter. By default, automocked getters now return `undefined`. You can keep using `vi.spyOn(object, name, 'get')` to spy on a getter and change its implementation
- The mock `vi.fn(implementation).mockReset()` now correctly returns the mock implementation in `.getMockImplementation()`
- `vi.fn().mock.invocationCallOrder` now starts with `1`, like Jest does, instead of `0`

### Standalone mode with filename filter

To improve user experience, Vitest will now start running the matched files when [`--standalone`](/guide/cli#standalone) is used with filename filter.
Expand Down Expand Up @@ -181,14 +214,26 @@ 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`
### `mock.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`.

### `mock.mock` is Persistent

Jest will recreate the mock state when `.mockClear` is called, meaning you always need to access it as a getter. Vitest, on the other hand, holds a persistent reference to the state, meaning you can reuse it:

```ts
const mock = vi.fn()
const state = mock.mock
mock.mockClear()

expect(state).toBe(mock.mock) // fails in Jest
```

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