Mocking a module

vi.mock()'s path argument needs to resolve to the same file that the module under test is using. If users.js imports <root>/src/client.js, vi.mock()'s path argument needs to match:

// users.js
import client from './client' // => resolves to path/to/client.js
// users.spec.js
vi.mock('../../client.js')    // => resolves to path/to/client.js

It often helps to use path aliases here.

Spying/mocking a function

To spy on or mock a function of the mocked module, do the following in test():

  1. Dynamically import the module, which gets the mocked module.
  2. Mock the function off of the mocked module reference, optionally returning a mock value. Since client.get() returns axios.get(), which returns a Promise, it makes sense to use mockResolvedValue() to mock the returned data.
// users.spec.js
import { describe, test, expect, vi } from 'vitest'
import UsersAPI from '@/users.js'

vi.mock('@/client')

describe('Users API', () => {
  test('Users API.getUsers', async () => {
    1️⃣
    const client = await import('@/client')

    2️⃣
    const response = { data: [{ id: 1, name: 'john doe' }] }
    client.default.get = vi.fn().mockResolvedValue(response)
    
    const users = await UsersAPI.getUsers()
    expect(client.default.get).toHaveBeenCalled()
    expect(users).toEqual(response)
  })
})

demo

Answer from tony19 on Stack Overflow
🌐
Vitest
vitest.dev › guide › mocking
Mocking | Guide | Vitest
April 8, 2026 - import * as mod from './example.js' vi.spyOn(mod, 'SomeClass').mockImplementation(class FakeClass { someMethod = vi.fn() })
Top answer
1 of 3
18

Mocking a module

vi.mock()'s path argument needs to resolve to the same file that the module under test is using. If users.js imports <root>/src/client.js, vi.mock()'s path argument needs to match:

// users.js
import client from './client' // => resolves to path/to/client.js
// users.spec.js
vi.mock('../../client.js')    // => resolves to path/to/client.js

It often helps to use path aliases here.

Spying/mocking a function

To spy on or mock a function of the mocked module, do the following in test():

  1. Dynamically import the module, which gets the mocked module.
  2. Mock the function off of the mocked module reference, optionally returning a mock value. Since client.get() returns axios.get(), which returns a Promise, it makes sense to use mockResolvedValue() to mock the returned data.
// users.spec.js
import { describe, test, expect, vi } from 'vitest'
import UsersAPI from '@/users.js'

vi.mock('@/client')

describe('Users API', () => {
  test('Users API.getUsers', async () => {
    1️⃣
    const client = await import('@/client')

    2️⃣
    const response = { data: [{ id: 1, name: 'john doe' }] }
    client.default.get = vi.fn().mockResolvedValue(response)
    
    const users = await UsersAPI.getUsers()
    expect(client.default.get).toHaveBeenCalled()
    expect(users).toEqual(response)
  })
})

demo

2 of 3
9

Late to the party but just in case anyone else is facing the same issue.

I solved it by importing the module dependency in the test file and mocking the whole module first, then just the methods I needed.

import { client } from 'client';

vi.mock('client', () => {
    const client = vi.fn();
    client.get = vi.fn();
    
    return { client }
});

Then in those tests calling client.get() behind the scenes as a dependency, just add

  client.get.mockResolvedValue({fakeResponse: []});

and the mocked function will be called instead of the real implementation.

If you are using a default export, look at the vitest docs since you need to provide a default key.

If mocking a module with a default export, you'll need to provide a default key within the returned factory function object. This is an ES modules specific caveat, therefore jest documentation may differ as jest uses commonJS modules.

Discussions

Cannot spy on function being called in the function tested when both functions are named exports from the same file
Describe the bug When two functions are exported from the same file as named exports, and you try to test that one of them is called by the other with vi.spyOn, the spy doesn't detect that the ... More on github.com
🌐 github.com
3
September 23, 2024
reactjs - How does spyOn and mock works in vitest? - Stack Overflow
I call spyOn on an imported module, but when I run the test, the actual implementation is passed to my testing module. More on stackoverflow.com
🌐 stackoverflow.com
spyOn not working
System: OS: Linux 5.15 Ubuntu 22.04.2 ... ~/.nvm/versions/node/v14.20.0/bin/npm Browsers: Chrome: 113.0.5672.126 npmPackages: @vitejs/plugin-vue: ^4.1.0 => 4.2.3 @vitest/coverage-v8: ^0.33.0 => 0.33.0 vite: ^4.3.9 => 4.3.9 vitest: ^0.33.0 => 0.33.0... More on github.com
🌐 github.com
8
August 26, 2023
[AskJS] Am I the only one that just cannot grasp how to mock in tests?
Do you understand how the CommonJS module system works? A lot of the "magic" here is because mocks work by hooking into the module system to change how and where require statements find their files. More on reddit.com
🌐 r/javascript
52
90
August 24, 2022
🌐
Vitest
vitest.dev › guide › mocking › modules
Mocking Modules | Vitest
February 7, 2026 - You only need to import the module as a namespace object in the file where you are using the vi.spyOn utility. If the answer is called in another file and is imported there as a named export, Vitest will be able to properly track it as long ...
🌐
Vitest
vitest.dev › api › mock
Mocks | Vitest
March 21, 2026 - You can create a mock function or a class to track its execution with the vi.fn method. If you want to track a property on an already created object, you can use the vi.spyOn method: js · import { vi } from 'vitest' const fn = vi.fn() fn('hello world') fn.mock.calls[0] === ['hello world'] const market = { getApples: () => 100 } const getApplesSpy = vi.spyOn(market, 'getApples') market.getApples() getApplesSpy.mock.calls.length === 1 ·
🌐
Laconic Wit
laconicwit.com › vi-mock-is-a-footgun-why-vi-spyon-should-be-your-default
`vi.mock` Is a Footgun: Why `vi.spyOn` Should Be Your Default
July 28, 2025 - vi.spyOn works by modifying an ES module’s live binding, a concept where the imported value stays linked to the original export. When the exporting module updates that value, the change is visible to the importer. That’s what makes spying possible in the first place. But there’s a catch: if your code saves a direct reference to an imported function, like when wrapping it in a higher-order function, the spy won’t affect it.
🌐
DEV Community
dev.to › erikpuk › how-to-mock-a-third-party-es6-export-in-vitest-38ff
How to spy on a third party ES6 export in Vitest - DEV Community
June 26, 2023 - Therefore you have to... In order to have something to spy on, you'll need to import that same module directly in your test. This should be the mock object that you returned in your factory above, with the vi.fn where you stuck it.
🌐
Steve Kinney
stevekinney.com › courses › introduction to testing › using spies
Using Spies | Introduction to Testing | Steve Kinney
March 17, 2026 - function handleTicketSaleError(errorMessage) { console.error(`Error: ${errorMessage}`); } In our test, we can spy on console.error to ensure it was called with the correct argument: import { describe, it, expect, vi } from 'vitest'; describe('handleTicketSaleError', () => { it('logs an error message when ticket sale fails', () => { // Spy on console.error const errorSpy = vi.spyOn(console, 'error'); // Call the function under test handleTicketSaleError('Payment declined'); // Assert that the spy tracked the correct call expect(errorSpy).toHaveBeenCalledWith('Error: Payment declined'); // Clean up: Restore the original function errorSpy.mockRestore(); }); }); In this example, errorSpy watches console.error and verifies that the correct error message was logged when handleTicketSaleError was called.
🌐
Vitest
vitest.dev › guide › mocking › functions
Mocking Functions | Vitest
August 31, 2025 - If you need to pass down a custom ... vi.spyOn and vi.fn share the same methods. ... import { afterEach, describe, expect, it, vi } from 'vitest' const messages = { items: [ ......
Find elsewhere
🌐
GitHub
github.com › vitest-dev › vitest › issues › 6551
Cannot spy on function being called in the function tested when both functions are named exports from the same file · Issue #6551 · vitest-dev/vitest
September 23, 2024 - (https://vitest.dev/api/vi.html#vi-spyon or https://vitest.dev/guide/common-errors) Example file being tested: export function helperFunction() { } export function testedFunction() { helperFunction(); } Example test file · import { describe, it, expect, vi } from "vitest"; import * as someModule from "~/someModule"; describe("some module tests") { describe("testedFunction") { it("calls helperFunction once") { const helperFunctionSpy = vi.spyOn(someModule, "helperFunction"); someModule.testedFunction(); expect(helperFunctionSpy).toHaveBeenCalledOnce(); // expected "helperFunction" to be called once, but got 0 times } } } I've also tried the following import variations: import * as someModule from "~/someModule"; const { testedFunction } = someModule; and ·
Author   vitest-dev
🌐
Vitest
v1.vitest.dev › guide › mocking
Mocking | Guide | Vitest v1.6
April 29, 2024 - We use 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. js · import { , , , , } from 'vitest' function ( = ..
🌐
Stack Overflow
stackoverflow.com › questions › 76620845 › how-does-spyon-and-mock-works-in-vitest
reactjs - How does spyOn and mock works in vitest? - Stack Overflow
//Test file import * as database from '../../database' import * as useDateHook from '../useDate' import * as useStorageHook from '../useStorage' import { useGameStatusStorage } from '.' const getStorageMock = vi.fn() const setStorageMock = vi.fn() const getTodayMock = vi.fn() const isSameDateMock = vi.fn() const getDailyWordMock = vi.fn() describe('useGameStatusStorage', () => { afterEach(() => vi.clearAllMocks()) vi.spyOn(useStorageHook, 'useStorage').mockReturnValue({ getStorage: getStorageMock, setStorage: setStorageMock, }) vi.spyOn(useDateHook, 'useDate').mockReturnValue({ getToday: getTo
🌐
Runebook
runebook.dev › english › articles › vitest
vi.spyOn() in Vitest: A Comprehensive Guide
Import vi Imports the vi object from vitest for mocking and spying. Define add Function Defines a simple add function that takes two numbers and returns their sum. Create a Spy Uses vi.spyOn(global, 'add') to create a spy on the global add function.
🌐
Maya Shavin
mayashavin.com › articles › two-shades-of-mocking-vitest
Two shades of mocking a function in Vitest
February 20, 2024 - This can potentially cause test-contamination, where the mock function from one test may affect the result of another test, which uses the original fetch. To solve this issue, we should save the original implementation of the global.fetch method before all the tests run within the same test suite: import { vi } from 'vitest'; describe ('fetchData', () => { let originalFetch; beforeAll(() => { originalFetch = global.fetch; global.fetch = vi.fn(mockedImplementation); }); // ...tests });
🌐
Medium
medium.com › @fagnersaraujo › mocking-read-only-imports-with-vitest-35449bd9aadd
Mocking read-only imports with Vitest | by Fagner Araujo | Medium
February 16, 2024 - import * as AmplifyAuth from 'aws-amplify/auth' vi.mock('aws-amplify/auth', () => ({ __esModule: true, signInWithRedirect: vi.fn() })) And now we can spy our method to make the check · beforeEach(() => { spySignInWithRedirect = vi.spyOn(AmplifyAuth, 'signInWithRedirect') }) Finally, the test can be done · test('should call SignIn function', async () => { ...
🌐
Vitest
vitest.dev › api › vi
Vi | Vitest
If there is no __mocks__ folder or a factory provided, Vitest will import the original module and auto-mock all its exports. For the rules applied, see algorithm. ... function doMock( path: string, factory?: MockOptions | MockFactory<unknown> ): Disposable function doMock<T>( module: Promise<T>, factory?: MockOptions | MockFactory<T> ): Disposable
🌐
LogRocket
blog.logrocket.com › home › an advanced guide to vitest testing and mocking
An advanced guide to Vitest testing and mocking - LogRocket Blog
June 17, 2024 - One approach is to create a spy to perform behavior verification after the test candidate, the fetchQuote function, is invoked. The spy tests whether the global fetch call has been called with the correct parameters. In addition, we can perform a state verification of the response: // quote.service.spy.test.ts import { describe, it, expect, vi } from "vitest"; import { fetchQuote } from "./quote.service"; describe("fetchQuote", () => { it("should return a valid quote", async () => { const fetchSpy = vi.spyOn(globalThis, "fetch"); // invoke the test candidate const response = await fetchQuote(); // behavior verification expect(fetchSpy).toHaveBeenCalledWith( "https://dummyjson.com/quotes/random", ); // state verification expect(response.quote).toBeDefined(); }); }); As we’ve learned, a spy usually doesn’t alter the implementation, so a real network call is invoked.
🌐
GitHub
github.com › vitest-dev › vitest › issues › 4023
spyOn not working · Issue #4023 vitest-dev/vitest · GitHub
August 26, 2023 - spyOn not working#4023 · Copy link · Labels · pending triage · sureshvv · opened · on Aug 26, 2023 · Issue body actions · ==== example.js ==== function sub1() { return 'hello' } function main1() { return sub1() } export { sub1, main1 } ==== example.test.js ==== import { describe, expect, it, vi } from "vitest"; import * as api from './example' describe('main', () => { it("example", () => { const spy1 = vi.spyOn(api, 'sub1') api.main1() expect(spy1).toHaveBeenCalledTimes(1) }) }) https://gist.githubusercontent.com/sureshvv/3c9825040be95692ae50181f537c9b39/raw/c89abc6009896acf69db0664824ca75ef94107cf/gistfile1.txt ·
Author   vitest-dev
🌐
DEV Community
dev.to › mayashavin › two-shades-of-mocking-a-function-in-vitest-41im
Two shades of mocking a function in Vitest - DEV Community
February 20, 2024 - This can potentially cause test-contamination, where the mock function from one test may affect the result of another test, which uses the original fetch. To solve this issue, we should save the original implementation of the global.fetch method before all the tests run within the same test suite: import { vi } from 'vitest'; describe ('fetchData', () => { let originalFetch; beforeAll(() => { originalFetch = global.fetch; global.fetch = vi.fn(mockedImplementation); }); // ...tests });
🌐
GitHub
github.com › vitest-dev › vitest › discussions › 1495
How to mock an imported function? · vitest-dev/vitest · Discussion #1495
Hi. Very simple question. How to mock a function from ts file in Vue3 component with script setup syntacsis? Component: import { toast } from "@/components/ui/toast" toast(...
Author   vitest-dev
🌐
Vitest
vitest.dev › guide › mocking › classes
Mocking Classes | Vitest
March 23, 2026 - 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 inheritance directly.