mirror of
https://github.com/vitest-dev/vitest.git
synced 2025-12-08 18:26:03 +00:00
159 lines
3.6 KiB
Markdown
159 lines
3.6 KiB
Markdown
# Mocking Classes
|
|
|
|
You can mock an entire class with a single [`vi.fn`](/api/vi#fn) call.
|
|
|
|
```ts
|
|
class Dog {
|
|
name: string
|
|
|
|
constructor(name: string) {
|
|
this.name = name
|
|
}
|
|
|
|
static getType(): string {
|
|
return 'animal'
|
|
}
|
|
|
|
greet = (): string => {
|
|
return `Hi! My name is ${this.name}!`
|
|
}
|
|
|
|
speak(): string {
|
|
return 'bark!'
|
|
}
|
|
|
|
isHungry() {}
|
|
feed() {}
|
|
}
|
|
```
|
|
|
|
We can re-create this class with `vi.fn` (or `vi.spyOn().mockImplementation()`):
|
|
|
|
```ts
|
|
const Dog = vi.fn(class {
|
|
static getType = vi.fn(() => 'mocked animal')
|
|
|
|
constructor(name) {
|
|
this.name = name
|
|
}
|
|
|
|
greet = vi.fn(() => `Hi! My name is ${this.name}!`)
|
|
speak = vi.fn(() => 'loud bark!')
|
|
feed = vi.fn()
|
|
})
|
|
```
|
|
|
|
::: warning
|
|
If a non-primitive is returned from the constructor function, that value will become the result of the new expression. In this case the `[[Prototype]]` may not be correctly bound:
|
|
|
|
```ts
|
|
const CorrectDogClass = vi.fn(function (name) {
|
|
this.name = name
|
|
})
|
|
|
|
const IncorrectDogClass = vi.fn(name => ({
|
|
name
|
|
}))
|
|
|
|
const Marti = new CorrectDogClass('Marti')
|
|
const Newt = new IncorrectDogClass('Newt')
|
|
|
|
Marti instanceof CorrectDogClass // ✅ true
|
|
Newt instanceof IncorrectDogClass // ❌ false!
|
|
```
|
|
|
|
If you are mocking classes, prefer the class syntax over the function.
|
|
:::
|
|
|
|
::: tip WHEN TO USE?
|
|
Generally speaking, you would re-create a class like this inside the module factory if the class is re-exported from another module:
|
|
|
|
```ts
|
|
import { Dog } from './dog.js'
|
|
|
|
vi.mock(import('./dog.js'), () => {
|
|
const Dog = vi.fn(class {
|
|
feed = vi.fn()
|
|
// ... other mocks
|
|
})
|
|
return { Dog }
|
|
})
|
|
```
|
|
|
|
This method can also be used to pass an instance of a class to a function that accepts the same interface:
|
|
|
|
```ts [src/feed.ts]
|
|
function feed(dog: Dog) {
|
|
// ...
|
|
}
|
|
```
|
|
```ts [tests/dog.test.ts]
|
|
import { expect, test, vi } from 'vitest'
|
|
import { feed } from '../src/feed.js'
|
|
|
|
const Dog = vi.fn(class {
|
|
feed = vi.fn()
|
|
})
|
|
|
|
test('can feed dogs', () => {
|
|
const dogMax = new Dog('Max')
|
|
|
|
feed(dogMax)
|
|
|
|
expect(dogMax.feed).toHaveBeenCalled()
|
|
expect(dogMax.isHungry()).toBe(false)
|
|
})
|
|
```
|
|
:::
|
|
|
|
Now, when we create a new instance of the `Dog` class its `speak` method (alongside `feed` and `greet`) is already mocked:
|
|
|
|
```ts
|
|
const Cooper = new Dog('Cooper')
|
|
Cooper.speak() // loud bark!
|
|
Cooper.greet() // Hi! My name is Cooper!
|
|
|
|
// you can use built-in assertions to check the validity of the call
|
|
expect(Cooper.speak).toHaveBeenCalled()
|
|
expect(Cooper.greet).toHaveBeenCalled()
|
|
|
|
const Max = new Dog('Max')
|
|
|
|
// methods are not shared between instances if you assigned them directly
|
|
expect(Max.speak).not.toHaveBeenCalled()
|
|
expect(Max.greet).not.toHaveBeenCalled()
|
|
```
|
|
|
|
We can reassign the return value for a specific instance:
|
|
|
|
```ts
|
|
const dog = new Dog('Cooper')
|
|
|
|
// "vi.mocked" is a type helper, since
|
|
// TypeScript doesn't know that Dog is a mocked class,
|
|
// it wraps any function in a Mock<T> type
|
|
// without validating if the function is a mock
|
|
vi.mocked(dog.speak).mockReturnValue('woof woof')
|
|
|
|
dog.speak() // woof woof
|
|
```
|
|
|
|
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:
|
|
|
|
```ts
|
|
const dog = new Dog('Cooper')
|
|
|
|
const nameSpy = vi.spyOn(dog, 'name', 'get').mockReturnValue('Max')
|
|
|
|
expect(dog.name).toBe('Max')
|
|
expect(nameSpy).toHaveBeenCalledTimes(1)
|
|
```
|
|
|
|
::: tip
|
|
You can also spy on getters and setters using the same method.
|
|
:::
|
|
|
|
::: danger
|
|
Using classes with `vi.fn()` was introduced in Vitest 4. Previously, you had to use `function` and `prototype` inheritence directly. See [v3 guide](https://v3.vitest.dev/guide/mocking.html#classes).
|
|
:::
|