Skip to content

Commit abc0d82

Browse files
authored
fix(spy)!: support spying on classes (#6160)
1 parent 924cb69 commit abc0d82

File tree

7 files changed

+311
-63
lines changed

7 files changed

+311
-63
lines changed

docs/api/vi.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,33 @@ expect(spy).toHaveBeenCalled()
459459
expect(spy).toHaveReturnedWith(1)
460460
```
461461

462+
If the spying method is a class definition, the mock implementations have to use the `function` or the `class` keyword:
463+
464+
```ts {12-14,16-20}
465+
const cart = {
466+
Apples: class Apples {
467+
getApples() {
468+
return 42
469+
}
470+
}
471+
}
472+
473+
const spy = vi.spyOn(cart, 'Apples')
474+
.mockImplementation(() => ({ getApples: () => 0 })) // [!code --]
475+
// with a function keyword
476+
.mockImplementation(function () {
477+
this.getApples = () => 0
478+
})
479+
// with a custom class
480+
.mockImplementation(class MockApples {
481+
getApples() {
482+
return 0
483+
}
484+
})
485+
```
486+
487+
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.
488+
462489
::: tip
463490
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:
464491

docs/guide/migration.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,37 @@ See also new guides:
8585
- [Including and excluding files from coverage report](/guide/coverage.html#including-and-excluding-files-from-coverage-report) for examples
8686
- [Profiling Test Performance | Code coverage](/guide/profiling-test-performance.html#code-coverage) for tips about debugging coverage generation
8787

88+
### `spyOn` Supports Constructors
89+
90+
Previously, if you tried to spy on a constructor with `vi.spyOn`, you would get an error like `Constructor <name> requires 'new'`. Since Vitest 4, all mocks called with a `new` keyword construct the instance instead of callying `mock.apply`. This means that the mock implementation has to use either the `function` or the `class` keyword in these cases:
91+
92+
```ts {12-14,16-20}
93+
const cart = {
94+
Apples: class Apples {
95+
getApples() {
96+
return 42
97+
}
98+
}
99+
}
100+
101+
const Spy = vi.spyOn(cart, 'Apples')
102+
.mockImplementation(() => ({ getApples: () => 0 })) // [!code --]
103+
// with a function keyword
104+
.mockImplementation(function () {
105+
this.getApples = () => 0
106+
})
107+
// with a custom class
108+
.mockImplementation(class MockApples {
109+
getApples() {
110+
return 0
111+
}
112+
})
113+
114+
const mock = new Spy()
115+
```
116+
117+
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.
118+
88119
### Deprecated APIs are Removed
89120

90121
Vitest 4.0 removes some deprecated APIs, including:

docs/guide/mocking.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,10 @@ import * as exports from './example.js'
764764
vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked')
765765
```
766766

767+
::: warning
768+
This will not work in the Browser Mode. For a workaround, see [Limitations](/guide/browser/#spying-on-module-exports).
769+
:::
770+
767771
### Mock an exported function
768772

769773
1. Example with `vi.mock`:
@@ -790,9 +794,29 @@ import * as exports from './example.js'
790794
vi.spyOn(exports, 'method').mockImplementation(() => {})
791795
```
792796

797+
::: warning
798+
`vi.spyOn` example will not work in the Browser Mode. For a workaround, see [Limitations](/guide/browser/#spying-on-module-exports).
799+
:::
800+
793801
### Mock an exported class implementation
794802

795-
1. Example with `vi.mock` and `.prototype`:
803+
1. Example with a fake `class`:
804+
```ts [example.js]
805+
export class SomeClass {}
806+
```
807+
```ts
808+
import { SomeClass } from './example.js'
809+
810+
vi.mock(import('./example.js'), () => {
811+
const SomeClass = vi.fn(class FakeClass {
812+
someMethod = vi.fn()
813+
})
814+
return { SomeClass }
815+
})
816+
// SomeClass.mock.instances will have SomeClass
817+
```
818+
819+
2. Example with `vi.mock` and `.prototype`:
796820
```ts [example.js]
797821
export class SomeClass {}
798822
```
@@ -807,7 +831,7 @@ vi.mock(import('./example.js'), () => {
807831
// SomeClass.mock.instances will have SomeClass
808832
```
809833

810-
2. Example with `vi.spyOn`:
834+
3. Example with `vi.spyOn`:
811835

812836
```ts
813837
import * as mod from './example.js'
@@ -818,6 +842,10 @@ SomeClass.prototype.someMethod = vi.fn()
818842
vi.spyOn(mod, 'SomeClass').mockImplementation(SomeClass)
819843
```
820844

845+
::: warning
846+
`vi.spyOn` example will not work in the Browser Mode. For a workaround, see [Limitations](/guide/browser/#spying-on-module-exports).
847+
:::
848+
821849
### Spy on an object returned from a function
822850

823851
1. Example using cache:

0 commit comments

Comments
 (0)