mirror of
https://github.com/vitest-dev/vitest.git
synced 2025-12-08 18:26:03 +00:00
874 lines
24 KiB
Markdown
874 lines
24 KiB
Markdown
---
|
|
title: Mocking | Guide
|
|
---
|
|
|
|
# Mocking
|
|
|
|
When writing tests it's only a matter of time before you need to create a "fake" version of an internal — or external — service. This is commonly referred to as **mocking**. Vitest provides utility functions to help you out through its **vi** helper. You can `import { vi } from 'vitest'` or access it **globally** (when [global configuration](/config/#globals) is **enabled**).
|
|
|
|
::: warning
|
|
Always remember to clear or restore mocks before or after each test run to undo mock state changes between runs! See [`mockReset`](/api/mock#mockreset) docs for more info.
|
|
:::
|
|
|
|
If you wanna dive in head first, check out the [API section](/api/vi) otherwise keep reading to take a deeper dive into the world of mocking.
|
|
|
|
## Dates
|
|
|
|
Sometimes you need to be in control of the date to ensure consistency when testing. Vitest uses [`@sinonjs/fake-timers`](https://github.com/sinonjs/fake-timers) package for manipulating timers, as well as system date. You can find more about the specific API in detail [here](/api/vi#vi-setsystemtime).
|
|
|
|
### Example
|
|
|
|
```js
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const businessHours = [9, 17]
|
|
|
|
function purchase() {
|
|
const currentHour = new Date().getHours()
|
|
const [open, close] = businessHours
|
|
|
|
if (currentHour > open && currentHour < close) {
|
|
return { message: 'Success' }
|
|
}
|
|
|
|
return { message: 'Error' }
|
|
}
|
|
|
|
describe('purchasing flow', () => {
|
|
beforeEach(() => {
|
|
// tell vitest we use mocked time
|
|
vi.useFakeTimers()
|
|
})
|
|
|
|
afterEach(() => {
|
|
// restoring date after each test run
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('allows purchases within business hours', () => {
|
|
// set hour within business hours
|
|
const date = new Date(2000, 1, 1, 13)
|
|
vi.setSystemTime(date)
|
|
|
|
// access Date.now() will result in the date set above
|
|
expect(purchase()).toEqual({ message: 'Success' })
|
|
})
|
|
|
|
it('disallows purchases outside of business hours', () => {
|
|
// set hour outside business hours
|
|
const date = new Date(2000, 1, 1, 19)
|
|
vi.setSystemTime(date)
|
|
|
|
// access Date.now() will result in the date set above
|
|
expect(purchase()).toEqual({ message: 'Error' })
|
|
})
|
|
})
|
|
```
|
|
|
|
## Functions
|
|
|
|
Mocking functions can be split up into two different categories; *spying & mocking*.
|
|
|
|
Sometimes all you need is to validate whether or not a specific function has been called (and possibly which arguments were passed). In these cases a spy would be all we need which you can use directly with `vi.spyOn()` ([read more here](/api/vi#vi-spyon)).
|
|
|
|
However spies can only help you **spy** on functions, they are not able to alter the implementation of those functions. In the case where we do need to create a fake (or mocked) version of a function we can use `vi.fn()` ([read more here](/api/vi#vi-fn)).
|
|
|
|
We use [Tinyspy](https://github.com/tinylibs/tinyspy) as a base for mocking functions, but we have our own wrapper to make it `jest` compatible. Both `vi.fn()` and `vi.spyOn()` share the same methods, however only the return result of `vi.fn()` is callable.
|
|
|
|
### Example
|
|
|
|
```js
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const messages = {
|
|
items: [
|
|
{ message: 'Simple test message', from: 'Testman' },
|
|
// ...
|
|
],
|
|
getLatest, // can also be a `getter or setter if supported`
|
|
}
|
|
|
|
function getLatest(index = messages.items.length - 1) {
|
|
return messages.items[index]
|
|
}
|
|
|
|
describe('reading messages', () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
|
|
it('should get the latest message with a spy', () => {
|
|
const spy = vi.spyOn(messages, 'getLatest')
|
|
expect(spy.getMockName()).toEqual('getLatest')
|
|
|
|
expect(messages.getLatest()).toEqual(
|
|
messages.items[messages.items.length - 1],
|
|
)
|
|
|
|
expect(spy).toHaveBeenCalledTimes(1)
|
|
|
|
spy.mockImplementationOnce(() => 'access-restricted')
|
|
expect(messages.getLatest()).toEqual('access-restricted')
|
|
|
|
expect(spy).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('should get with a mock', () => {
|
|
const mock = vi.fn().mockImplementation(getLatest)
|
|
|
|
expect(mock()).toEqual(messages.items[messages.items.length - 1])
|
|
expect(mock).toHaveBeenCalledTimes(1)
|
|
|
|
mock.mockImplementationOnce(() => 'access-restricted')
|
|
expect(mock()).toEqual('access-restricted')
|
|
|
|
expect(mock).toHaveBeenCalledTimes(2)
|
|
|
|
expect(mock()).toEqual(messages.items[messages.items.length - 1])
|
|
expect(mock).toHaveBeenCalledTimes(3)
|
|
})
|
|
})
|
|
```
|
|
|
|
### More
|
|
|
|
- [Jest's Mock Functions](https://jestjs.io/docs/mock-function-api)
|
|
|
|
## Globals
|
|
|
|
You can mock global variables that are not present with `jsdom` or `node` by using [`vi.stubGlobal`](/api/vi#vi-stubglobal) helper. It will put the value of the global variable into a `globalThis` object.
|
|
|
|
```ts
|
|
import { vi } from 'vitest'
|
|
|
|
const IntersectionObserverMock = vi.fn(() => ({
|
|
disconnect: vi.fn(),
|
|
observe: vi.fn(),
|
|
takeRecords: vi.fn(),
|
|
unobserve: vi.fn(),
|
|
}))
|
|
|
|
vi.stubGlobal('IntersectionObserver', IntersectionObserverMock)
|
|
|
|
// now you can access it as `IntersectionObserver` or `window.IntersectionObserver`
|
|
```
|
|
|
|
## Modules
|
|
|
|
Mock modules observe third-party-libraries, that are invoked in some other code, allowing you to test arguments, output or even redeclare its implementation.
|
|
|
|
See the [`vi.mock()` API section](/api/vi#vi-mock) for a more in-depth detailed API description.
|
|
|
|
### Automocking Algorithm
|
|
|
|
If your code is importing a mocked module, without any associated `__mocks__` file or `factory` for this module, Vitest will mock the module itself by invoking it and mocking every export.
|
|
|
|
The following principles apply
|
|
* All arrays will be emptied
|
|
* All primitives and collections will stay the same
|
|
* All objects will be deeply cloned
|
|
* All instances of classes and their prototypes will be deeply cloned
|
|
|
|
### Virtual Modules
|
|
|
|
Vitest supports mocking Vite [virtual modules](https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention). It works differently from how virtual modules are treated in Jest. Instead of passing down `virtual: true` to a `vi.mock` function, you need to tell Vite that module exists otherwise it will fail during parsing. You can do that in several ways:
|
|
|
|
1. Provide an alias
|
|
|
|
```ts
|
|
// vitest.config.js
|
|
export default {
|
|
test: {
|
|
alias: {
|
|
'$app/forms': resolve('./mocks/forms.js')
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
2. Provide a plugin that resolves a virtual module
|
|
|
|
```ts
|
|
// vitest.config.js
|
|
export default {
|
|
plugins: [
|
|
{
|
|
name: 'virtual-modules',
|
|
resolveId(id) {
|
|
if (id === '$app/forms') {
|
|
return 'virtual:$app/forms'
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
The benefit of the second approach is that you can dynamically create different virtual entrypoints. If you redirect several virtual modules into a single file, then all of them will be affected by `vi.mock`, so make sure to use unique identifiers.
|
|
|
|
### Mocking Pitfalls
|
|
|
|
Beware that it is not possible to mock calls to methods that are called inside other methods of the same file. For example, in this code:
|
|
|
|
```ts
|
|
export function foo() {
|
|
return 'foo'
|
|
}
|
|
|
|
export function foobar() {
|
|
return `${foo()}bar`
|
|
}
|
|
```
|
|
|
|
It is not possible to mock the `foo` method from the outside because it is referenced directly. So this code will have no effect on the `foo` call inside `foobar` (but it will affect the `foo` call in other modules):
|
|
|
|
```ts
|
|
import { vi } from 'vitest'
|
|
import * as mod from './foobar.js'
|
|
|
|
// this will only affect "foo" outside of the original module
|
|
vi.spyOn(mod, 'foo')
|
|
vi.mock('./foobar.js', async (importOriginal) => {
|
|
return {
|
|
...await importOriginal<typeof import('./foobar.js')>(),
|
|
// this will only affect "foo" outside of the original module
|
|
foo: () => 'mocked'
|
|
}
|
|
})
|
|
```
|
|
|
|
You can confirm this behaviour by providing the implementation to the `foobar` method directly:
|
|
|
|
```ts
|
|
// foobar.test.js
|
|
import * as mod from './foobar.js'
|
|
|
|
vi.spyOn(mod, 'foo')
|
|
|
|
// exported foo references mocked method
|
|
mod.foobar(mod.foo)
|
|
```
|
|
|
|
```ts
|
|
// foobar.js
|
|
export function foo() {
|
|
return 'foo'
|
|
}
|
|
|
|
export function foobar(injectedFoo) {
|
|
return injectedFoo === foo // false
|
|
}
|
|
```
|
|
|
|
This is the intended behaviour. It is usually a sign of bad code when mocking is involved in such a manner. Consider refactoring your code into multiple files or improving your application architecture by using techniques such as [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection).
|
|
|
|
### Example
|
|
|
|
```js
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { Client } from 'pg'
|
|
import { failure, success } from './handlers.js'
|
|
|
|
// get todos
|
|
export async function getTodos(event, context) {
|
|
const client = new Client({
|
|
// ...clientOptions
|
|
})
|
|
|
|
await client.connect()
|
|
|
|
try {
|
|
const result = await client.query('SELECT * FROM todos;')
|
|
|
|
client.end()
|
|
|
|
return success({
|
|
message: `${result.rowCount} item(s) returned`,
|
|
data: result.rows,
|
|
status: true,
|
|
})
|
|
}
|
|
catch (e) {
|
|
console.error(e.stack)
|
|
|
|
client.end()
|
|
|
|
return failure({ message: e, status: false })
|
|
}
|
|
}
|
|
|
|
vi.mock('pg', () => {
|
|
const Client = vi.fn()
|
|
Client.prototype.connect = vi.fn()
|
|
Client.prototype.query = vi.fn()
|
|
Client.prototype.end = vi.fn()
|
|
|
|
return { Client }
|
|
})
|
|
|
|
vi.mock('./handlers.js', () => {
|
|
return {
|
|
success: vi.fn(),
|
|
failure: vi.fn(),
|
|
}
|
|
})
|
|
|
|
describe('get a list of todo items', () => {
|
|
let client
|
|
|
|
beforeEach(() => {
|
|
client = new Client()
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('should return items successfully', async () => {
|
|
client.query.mockResolvedValueOnce({ rows: [], rowCount: 0 })
|
|
|
|
await getTodos()
|
|
|
|
expect(client.connect).toBeCalledTimes(1)
|
|
expect(client.query).toBeCalledWith('SELECT * FROM todos;')
|
|
expect(client.end).toBeCalledTimes(1)
|
|
|
|
expect(success).toBeCalledWith({
|
|
message: '0 item(s) returned',
|
|
data: [],
|
|
status: true,
|
|
})
|
|
})
|
|
|
|
it('should throw an error', async () => {
|
|
const mError = new Error('Unable to retrieve rows')
|
|
client.query.mockRejectedValueOnce(mError)
|
|
|
|
await getTodos()
|
|
|
|
expect(client.connect).toBeCalledTimes(1)
|
|
expect(client.query).toBeCalledWith('SELECT * FROM todos;')
|
|
expect(client.end).toBeCalledTimes(1)
|
|
expect(failure).toBeCalledWith({ message: mError, status: false })
|
|
})
|
|
})
|
|
```
|
|
|
|
## File System
|
|
|
|
Mocking the file system ensures that the tests do not depend on the actual file system, making the tests more reliable and predictable. This isolation helps in avoiding side effects from previous tests. It allows for testing error conditions and edge cases that might be difficult or impossible to replicate with an actual file system, such as permission issues, disk full scenarios, or read/write errors.
|
|
|
|
Vitest doesn't provide any file system mocking API out of the box. You can use `vi.mock` to mock the `fs` module manually, but it's hard to maintain. Instead, we recommend using [`memfs`](https://www.npmjs.com/package/memfs) to do that for you. `memfs` creates an in-memory file system, which simulates file system operations without touching the actual disk. This approach is fast and safe, avoiding any potential side effects on the real file system.
|
|
|
|
### Example
|
|
|
|
To automatically redirect every `fs` call to `memfs`, you can create `__mocks__/fs.cjs` and `__mocks__/fs/promises.cjs` files at the root of your project:
|
|
|
|
::: code-group
|
|
```ts [__mocks__/fs.cjs]
|
|
// we can also use `import`, but then
|
|
// every export should be explicitly defined
|
|
|
|
const { fs } = require('memfs')
|
|
module.exports = fs
|
|
```
|
|
|
|
```ts [__mocks__/fs/promises.cjs]
|
|
// we can also use `import`, but then
|
|
// every export should be explicitly defined
|
|
|
|
const { fs } = require('memfs')
|
|
module.exports = fs.promises
|
|
```
|
|
:::
|
|
|
|
```ts
|
|
// read-hello-world.js
|
|
import { readFileSync } from 'node:fs'
|
|
|
|
export function readHelloWorld(path) {
|
|
return readFileSync(path)
|
|
}
|
|
```
|
|
|
|
```ts
|
|
// hello-world.test.js
|
|
import { beforeEach, expect, it, vi } from 'vitest'
|
|
import { fs, vol } from 'memfs'
|
|
import { readHelloWorld } from './read-hello-world.js'
|
|
|
|
// tell vitest to use fs mock from __mocks__ folder
|
|
// this can be done in a setup file if fs should always be mocked
|
|
vi.mock('node:fs')
|
|
vi.mock('node:fs/promises')
|
|
|
|
beforeEach(() => {
|
|
// reset the state of in-memory fs
|
|
vol.reset()
|
|
})
|
|
|
|
it('should return correct text', () => {
|
|
const path = '/hello-world.txt'
|
|
fs.writeFileSync(path, 'hello world')
|
|
|
|
const text = readHelloWorld(path)
|
|
expect(text).toBe('hello world')
|
|
})
|
|
|
|
it('can return a value multiple times', () => {
|
|
// you can use vol.fromJSON to define several files
|
|
vol.fromJSON(
|
|
{
|
|
'./dir1/hw.txt': 'hello dir1',
|
|
'./dir2/hw.txt': 'hello dir2',
|
|
},
|
|
// default cwd
|
|
'/tmp',
|
|
)
|
|
|
|
expect(readHelloWorld('/tmp/dir1/hw.txt')).toBe('hello dir1')
|
|
expect(readHelloWorld('/tmp/dir2/hw.txt')).toBe('hello dir2')
|
|
})
|
|
```
|
|
|
|
## Requests
|
|
|
|
Because Vitest runs in Node, mocking network requests is tricky; web APIs are not available, so we need something that will mimic network behavior for us. We recommend [Mock Service Worker](https://mswjs.io/) to accomplish this. It will let you mock both `REST` and `GraphQL` network requests, and is framework agnostic.
|
|
|
|
Mock Service Worker (MSW) works by intercepting the requests your tests make, allowing you to use it without changing any of your application code. In-browser, this uses the [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API). In Node.js, and for Vitest, it uses the [`@mswjs/interceptors`](https://github.com/mswjs/interceptors) library. To learn more about MSW, read their [introduction](https://mswjs.io/docs/)
|
|
|
|
### Configuration
|
|
|
|
You can use it like below in your [setup file](/config/#setupfiles)
|
|
```js
|
|
import { afterAll, afterEach, beforeAll } from 'vitest'
|
|
import { setupServer } from 'msw/node'
|
|
import { HttpResponse, graphql, http } from 'msw'
|
|
|
|
const posts = [
|
|
{
|
|
userId: 1,
|
|
id: 1,
|
|
title: 'first post title',
|
|
body: 'first post body',
|
|
},
|
|
// ...
|
|
]
|
|
|
|
export const restHandlers = [
|
|
http.get('https://rest-endpoint.example/path/to/posts', () => {
|
|
return HttpResponse.json(posts)
|
|
}),
|
|
]
|
|
|
|
const graphqlHandlers = [
|
|
graphql.query('ListPosts', () => {
|
|
return HttpResponse.json(
|
|
{
|
|
data: { posts },
|
|
},
|
|
)
|
|
}),
|
|
]
|
|
|
|
const server = setupServer(...restHandlers, ...graphqlHandlers)
|
|
|
|
// Start server before all tests
|
|
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
|
|
|
|
// Close server after all tests
|
|
afterAll(() => server.close())
|
|
|
|
// Reset handlers after each test `important for test isolation`
|
|
afterEach(() => server.resetHandlers())
|
|
```
|
|
|
|
> Configuring the server with `onUnhandleRequest: 'error'` ensures that an error is thrown whenever there is a request that does not have a corresponding request handler.
|
|
|
|
### More
|
|
There is much more to MSW. You can access cookies and query parameters, define mock error responses, and much more! To see all you can do with MSW, read [their documentation](https://mswjs.io/docs).
|
|
|
|
## Timers
|
|
|
|
When we test code that involves timeouts or intervals, instead of having our tests wait it out or timeout, we can speed up our tests by using "fake" timers that mock calls to `setTimeout` and `setInterval`.
|
|
|
|
See the [`vi.useFakeTimers` API section](/api/vi#vi-usefaketimers) for a more in depth detailed API description.
|
|
|
|
### Example
|
|
|
|
```js
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
function executeAfterTwoHours(func) {
|
|
setTimeout(func, 1000 * 60 * 60 * 2) // 2 hours
|
|
}
|
|
|
|
function executeEveryMinute(func) {
|
|
setInterval(func, 1000 * 60) // 1 minute
|
|
}
|
|
|
|
const mock = vi.fn(() => console.log('executed'))
|
|
|
|
describe('delayed execution', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers()
|
|
})
|
|
afterEach(() => {
|
|
vi.restoreAllMocks()
|
|
})
|
|
it('should execute the function', () => {
|
|
executeAfterTwoHours(mock)
|
|
vi.runAllTimers()
|
|
expect(mock).toHaveBeenCalledTimes(1)
|
|
})
|
|
it('should not execute the function', () => {
|
|
executeAfterTwoHours(mock)
|
|
// advancing by 2ms won't trigger the func
|
|
vi.advanceTimersByTime(2)
|
|
expect(mock).not.toHaveBeenCalled()
|
|
})
|
|
it('should execute every minute', () => {
|
|
executeEveryMinute(mock)
|
|
vi.advanceTimersToNextTimer()
|
|
expect(mock).toHaveBeenCalledTimes(1)
|
|
vi.advanceTimersToNextTimer()
|
|
expect(mock).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|
|
```
|
|
|
|
## Classes
|
|
|
|
You can mock an entire class with a single `vi.fn` call - since all classes are also functions, this works out of the box. Beware that currently Vitest doesn't respect the `new` keyword so the `new.target` is always `undefined` in the body of a function.
|
|
|
|
```ts
|
|
class Dog {
|
|
name: string
|
|
|
|
constructor(name: string) {
|
|
this.name = name
|
|
}
|
|
|
|
static getType(): string {
|
|
return 'animal'
|
|
}
|
|
|
|
speak(): string {
|
|
return 'bark!'
|
|
}
|
|
|
|
isHungry() {}
|
|
feed() {}
|
|
}
|
|
```
|
|
|
|
We can re-create this class with ES5 functions:
|
|
|
|
```ts
|
|
const Dog = vi.fn(function (name) {
|
|
this.name = name
|
|
})
|
|
|
|
// notice that static methods are mocked directly on the function,
|
|
// not on the instance of the class
|
|
Dog.getType = vi.fn(() => 'mocked animal')
|
|
|
|
// mock the "speak" and "feed" methods on every instance of a class
|
|
// all `new Dog()` instances will inherit these spies
|
|
Dog.prototype.speak = vi.fn(() => 'loud bark!')
|
|
Dog.prototype.feed = vi.fn()
|
|
```
|
|
|
|
::: 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()
|
|
Dog.prototype.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) {
|
|
// ...
|
|
}
|
|
|
|
// ./tests/dog.test.ts
|
|
import { expect, test, vi } from 'vitest'
|
|
import { feed } from '../src/feed.js'
|
|
|
|
const Dog = vi.fn()
|
|
Dog.prototype.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`) is already mocked:
|
|
|
|
```ts
|
|
const dog = new Dog('Cooper')
|
|
dog.speak() // loud bark!
|
|
|
|
// you can use built-in assertions to check the validity of the call
|
|
expect(dog.speak).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 MockInstance<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.
|
|
:::
|
|
|
|
## Cheat Sheet
|
|
|
|
:::info
|
|
`vi` in the examples below is imported directly from `vitest`. You can also use it globally, if you set `globals` to `true` in your [config](/config/).
|
|
:::
|
|
|
|
I want to…
|
|
|
|
### Mock exported variables
|
|
```js
|
|
// some-path.js
|
|
export const getter = 'variable'
|
|
```
|
|
```ts
|
|
// some-path.test.ts
|
|
import * as exports from './some-path.js'
|
|
|
|
vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked')
|
|
```
|
|
|
|
### Mock an exported function
|
|
|
|
1. Example with `vi.mock`:
|
|
|
|
::: warning
|
|
Don't forget that a `vi.mock` call is hoisted to top of the file. It will always be executed before all imports.
|
|
:::
|
|
|
|
```ts
|
|
// ./some-path.js
|
|
export function method() {}
|
|
```
|
|
```ts
|
|
import { method } from './some-path.js'
|
|
|
|
vi.mock('./some-path.js', () => ({
|
|
method: vi.fn()
|
|
}))
|
|
```
|
|
|
|
2. Example with `vi.spyOn`:
|
|
```ts
|
|
import * as exports from './some-path.js'
|
|
|
|
vi.spyOn(exports, 'method').mockImplementation(() => {})
|
|
```
|
|
|
|
### Mock an exported class implementation
|
|
|
|
1. Example with `vi.mock` and `.prototype`:
|
|
```ts
|
|
// ./some-path.ts
|
|
export class SomeClass {}
|
|
```
|
|
```ts
|
|
import { SomeClass } from './some-path.js'
|
|
|
|
vi.mock(import('./some-path.js'), () => {
|
|
const SomeClass = vi.fn()
|
|
SomeClass.prototype.someMethod = vi.fn()
|
|
return { SomeClass }
|
|
})
|
|
// SomeClass.mock.instances will have SomeClass
|
|
```
|
|
|
|
2. Example with `vi.spyOn`:
|
|
|
|
```ts
|
|
import * as mod from './some-path.js'
|
|
|
|
const SomeClass = vi.fn()
|
|
SomeClass.prototype.someMethod = vi.fn()
|
|
|
|
vi.spyOn(mod, 'SomeClass').mockImplementation(SomeClass)
|
|
```
|
|
|
|
### Spy on an object returned from a function
|
|
|
|
1. Example using cache:
|
|
|
|
```ts
|
|
// some-path.ts
|
|
export function useObject() {
|
|
return { method: () => true }
|
|
}
|
|
```
|
|
|
|
```ts
|
|
// useObject.js
|
|
import { useObject } from './some-path.js'
|
|
|
|
const obj = useObject()
|
|
obj.method()
|
|
```
|
|
|
|
```ts
|
|
// useObject.test.js
|
|
import { useObject } from './some-path.js'
|
|
|
|
vi.mock(import('./some-path.js'), () => {
|
|
let _cache
|
|
const useObject = () => {
|
|
if (!_cache) {
|
|
_cache = {
|
|
method: vi.fn(),
|
|
}
|
|
}
|
|
// now every time that useObject() is called it will
|
|
// return the same object reference
|
|
return _cache
|
|
}
|
|
return { useObject }
|
|
})
|
|
|
|
const obj = useObject()
|
|
// obj.method was called inside some-path
|
|
expect(obj.method).toHaveBeenCalled()
|
|
```
|
|
|
|
### Mock part of a module
|
|
|
|
```ts
|
|
import { mocked, original } from './some-path.js'
|
|
|
|
vi.mock(import('./some-path.js'), async (importOriginal) => {
|
|
const mod = await importOriginal()
|
|
return {
|
|
...mod,
|
|
mocked: vi.fn()
|
|
}
|
|
})
|
|
original() // has original behaviour
|
|
mocked() // is a spy function
|
|
```
|
|
|
|
::: warning
|
|
Don't forget that this only [mocks _external_ access](#mocking-pitfalls). In this example, if `original` calls `mocked` internally, it will always call the function defined in the module, not in the mock factory.
|
|
:::
|
|
|
|
### Mock the current date
|
|
|
|
To mock `Date`'s time, you can use `vi.setSystemTime` helper function. This value will **not** automatically reset between different tests.
|
|
|
|
Beware that using `vi.useFakeTimers` also changes the `Date`'s time.
|
|
|
|
```ts
|
|
const mockDate = new Date(2022, 0, 1)
|
|
vi.setSystemTime(mockDate)
|
|
const now = new Date()
|
|
expect(now.valueOf()).toBe(mockDate.valueOf())
|
|
// reset mocked time
|
|
vi.useRealTimers()
|
|
```
|
|
|
|
### Mock a global variable
|
|
|
|
You can set global variable by assigning a value to `globalThis` or using [`vi.stubGlobal`](/api/vi#vi-stubglobal) helper. When using `vi.stubGlobal`, it will **not** automatically reset between different tests, unless you enable [`unstubGlobals`](/config/#unstubglobals) config option or call [`vi.unstubAllGlobals`](/api/vi#vi-unstuballglobals).
|
|
|
|
```ts
|
|
vi.stubGlobal('__VERSION__', '1.0.0')
|
|
expect(__VERSION__).toBe('1.0.0')
|
|
```
|
|
|
|
### Mock `import.meta.env`
|
|
|
|
1. To change environmental variable, you can just assign a new value to it.
|
|
|
|
::: warning
|
|
The environmental variable value will **_not_** automatically reset between different tests.
|
|
:::
|
|
|
|
```ts
|
|
import { beforeEach, expect, it } from 'vitest'
|
|
|
|
// you can reset it in beforeEach hook manually
|
|
const originalViteEnv = import.meta.env.VITE_ENV
|
|
|
|
beforeEach(() => {
|
|
import.meta.env.VITE_ENV = originalViteEnv
|
|
})
|
|
|
|
it('changes value', () => {
|
|
import.meta.env.VITE_ENV = 'staging'
|
|
expect(import.meta.env.VITE_ENV).toBe('staging')
|
|
})
|
|
```
|
|
|
|
2. If you want to automatically reset the value(s), you can use the `vi.stubEnv` helper with the [`unstubEnvs`](/config/#unstubenvs) config option enabled (or call [`vi.unstubAllEnvs`](/api/vi#vi-unstuballenvs) manually in a `beforeEach` hook):
|
|
|
|
```ts
|
|
import { expect, it, vi } from 'vitest'
|
|
|
|
// before running tests "VITE_ENV" is "test"
|
|
import.meta.env.VITE_ENV === 'test'
|
|
|
|
it('changes value', () => {
|
|
vi.stubEnv('VITE_ENV', 'staging')
|
|
expect(import.meta.env.VITE_ENV).toBe('staging')
|
|
})
|
|
|
|
it('the value is restored before running an other test', () => {
|
|
expect(import.meta.env.VITE_ENV).toBe('test')
|
|
})
|
|
```
|
|
|
|
```ts
|
|
// vitest.config.ts
|
|
export default defineConfig({
|
|
test: {
|
|
unstubEnvs: true,
|
|
},
|
|
})
|
|
```
|