You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Iftheimplementationisaclass, themock's `prototype` is re-pointed to the implementation'sprototype, soconstructedinstancesseeitsprototypemethodsandpass`instanceof`checksagainstit. See [MockingClasses](/guide/mocking/classes) fordetails.
170
+
169
171
## mockImplementationOnce
170
172
171
173
```ts
@@ -284,6 +286,8 @@ Does what [`mockClear`](#mockClear) does and resets the mock implementation. Thi
284
286
Notethatresettingamockfrom`vi.fn()`willsettheimplementationtoanemptyfunction that returns `undefined`.
285
287
Resetting a mock from `vi.fn(impl)` will reset the implementation to `impl`.
286
288
289
+
Themock's `prototype` chain follows along: it reverts to the original class for `vi.fn(impl)` and `vi.spyOn()`, and to a plain object for `vi.fn()`, so instances constructed after the reset no longer pass `instanceof` checks against a previously set class implementation.
Copy file name to clipboardExpand all lines: docs/api/vi.md
+8-1Lines changed: 8 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -473,13 +473,18 @@ You can also pass down a class to `vi.fn`:
473
473
474
474
```ts
475
475
const Cart = vi.fn(class {
476
-
get= () =>0
476
+
get() {
477
+
return 0
478
+
}
477
479
})
478
480
479
481
const cart = new Cart()
480
482
expect(Cart).toHaveBeenCalled()
483
+
expect(cart.get()).toBe(0)
481
484
```
482
485
486
+
Instances keep the prototype chain of the implementation class, so its prototype methods are available on instances, and `instanceof` checks against the implementation class pass. See [Mocking Classes](/guide/mocking/classes) for details.
If you provide an arrow function, you will get [`<anonymous>isnotaconstructor` error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Not_a_constructor) when the mock is called.
616
621
622
+
With a class implementation, instances keep the prototype chain of that class: prototype methods like `getApples` are available on instances, and `instanceof` checks against the implementation class pass. See [Mocking Classes](/guide/mocking/classes) for details.
623
+
617
624
::: tip
618
625
In environments that support [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management), you can use `using` instead of `const` to automatically call `mockRestore` on any mocked function when the containing block is exited. This is especially useful for spied methods:
Copy file name to clipboardExpand all lines: docs/guide/migration.md
+26Lines changed: 26 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -217,6 +217,32 @@ In browser mode, mock metadata is serialized between Vitest and the test iframe.
217
217
218
218
Automocks are now restored as automocks. If a browser test relied on the original implementation running through an automocked module, its exports now return `undefined` by default. Pass [`{ spy: true }`](/api/vi#vi-mock) to keep calling the real implementation while still tracking calls, or provide a factory with the behavior you need.
219
219
220
+
### Class Mocks Keep Prototype Methods
221
+
222
+
Instances created from a class mock previously inherited from the mock's own empty `prototype`. Methods defined with the regular class syntax were `undefined` on instances, even inside the constructor, and `instanceof` checks against the implementation class failed. This affected [`vi.fn(Dog)`](/api/vi#vi-fn), `vi.spyOn(obj, 'Dog')` with or without a mock implementation, and [`.mockImplementation(class ...)`](/api/mock#mockimplementation).
223
+
224
+
The mock's `prototype` is now chained to the implementation's prototype as soon as the implementation is set, and kept in sync when it changes, so instances behave like instances of the implementation class:
225
+
226
+
```ts
227
+
classDog {
228
+
speak() {
229
+
return'bark!'
230
+
}
231
+
}
232
+
233
+
const MockedDog =vi.fn(Dog)
234
+
const dog =newMockedDog()
235
+
236
+
typeofdog.speak// was 'undefined', now 'function'
237
+
doginstanceofDog// was false, now true
238
+
doginstanceofMockedDog// true, as before
239
+
240
+
// the chain is visible on the mock itself
241
+
Object.getPrototypeOf(MockedDog.prototype) // was Object.prototype, now Dog.prototype
242
+
```
243
+
244
+
Overriding methods on the mock's `prototype` still works and shadows the implementation. [`mockReset`](/api/mock#mockreset) reverts the chain together with the implementation: back to the original class for `vi.fn(Dog)` and `vi.spyOn()`, and to a plain object for `vi.fn()`. See [Mocking Classes](/guide/mocking/classes) for details.
245
+
220
246
### Benchmarking API Rewrite
221
247
222
248
The benchmarking API has been rewritten. `bench` is no longer a top-level import from `vitest`; it is a [test-context fixture](/guide/test-context#bench) accessed from inside a regular `test()`. See the [Benchmarking guide](/guide/benchmarking) for the new API.
Copy file name to clipboardExpand all lines: docs/guide/mocking/classes.md
+74-3Lines changed: 74 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -27,7 +27,12 @@ class Dog {
27
27
}
28
28
```
29
29
30
-
We can re-create this class with `vi.fn` (or `vi.spyOn().mockImplementation()`):
30
+
Notice that this class defines its members in two different ways, and the difference matters for mocking:
31
+
32
+
-`greet` is a class field. The assignment runs during construction, so every instance gets its own copy of the function as its own property.
33
+
-`speak`, `isHungry`, and `feed` are prototype methods. They are created once and stored on `Dog.prototype`, an object that all instances share. An instance doesn't have a `speak` property of its own: when you call `dog.speak()`, JavaScript doesn't find `speak` on the instance and continues looking on `Dog.prototype`. Every instance finds the same function there, so `dog.speak === Dog.prototype.speak` is `true`.
34
+
35
+
We can re-create this class with `vi.fn` (or `vi.spyOn().mockImplementation()`). By defining every method as a class field, each instance gets its own separate mock, which allows checking calls on a single instance:
31
36
32
37
```ts
33
38
const Dog =vi.fn(class {
@@ -93,6 +98,7 @@ import { feed } from '../src/feed.js'
We can reassign the return value for a specific instance:
133
+
You don't have to redefine every method as a class field. Instances keep the prototype chain of the class you pass to `vi.fn`, so prototype methods stay available on instances, both during and after construction, and instances pass `instanceof` checks against that class:
134
+
135
+
```ts
136
+
classOriginalDog {
137
+
constructor(name) {
138
+
this.name=name
139
+
}
140
+
141
+
speak() {
142
+
return'bark!'
143
+
}
144
+
}
145
+
146
+
const MockedDog =vi.fn(OriginalDog)
147
+
const dog =newMockedDog('Cooper')
148
+
149
+
dog.speak() // bark!
150
+
doginstanceofMockedDog// true
151
+
doginstanceofOriginalDog// true
152
+
```
153
+
154
+
Note that nothing is mocked in this example. Unlike the `speak = vi.fn()` field in the `Dog` example above, the instance doesn't receive its own mock function. `dog.speak` is found through the prototype chain and refers to the original class method (`dog.speak === MockedDog.prototype.speak`), so call assertions throw:
155
+
156
+
```ts
157
+
expect(dog.speak).toHaveBeenCalled()
158
+
// TypeError: [Function speak] is not a spy or a call to a spy!
159
+
```
160
+
161
+
Since every instance finds `speak` on the prototype, you can mock it for all of them at once by assigning a mock there:
162
+
163
+
```ts
164
+
MockedDog.prototype.speak=vi.fn(() =>'woof!')
165
+
166
+
const cooper =newMockedDog('Cooper')
167
+
const max =newMockedDog('Max')
168
+
169
+
cooper.speak() // woof!
170
+
max.speak() // woof!
171
+
172
+
// calls from both instances are recorded by the same mock
Assigning on `MockedDog.prototype` instead of `OriginalDog.prototype` keeps the original class untouched: the lookup order is `instance` → `MockedDog.prototype` → `OriginalDog.prototype`, so the assigned function shadows the original method. Because instances look the method up on every call rather than keeping a copy, the mock is visible to all of them, even those created before the assignment. The trade-off is that they also share a single call history, unlike class fields, which give every instance its own mock.
179
+
180
+
::: warning
181
+
The mock's `prototype` always follows the current implementation: it is re-pointed when you set a new implementation, when a queued `mockImplementationOnce` class is constructed, and when the mock is reset. If a single mock uses different class implementations, instances created by earlier implementations lose access to their prototype methods once a newer implementation takes over. Own properties assigned in the constructor or via class fields are not affected.
182
+
:::
183
+
184
+
If you want to mock the method of one instance only, use [`vi.spyOn`](/api/vi#vi-spyon). It defines the mock directly on that instance, shadowing the prototype method just for it:
When methods are defined as class fields, like in the mocked `Dog` class at the top of this page, every instance already has its own mock, so you can reassign the return value for a specific instance directly:
To mock the property, we can use the `vi.spyOn(dog, 'name', 'get')` method. This makes it possible to use spy assertions on the mocked property:
212
+
To mock a non-function property, like `name`, we can use the `vi.spyOn(dog, 'name', 'get')` method. This makes it possible to use spy assertions on the mocked property:
0 commit comments