Vitest Interview Questions & Answers (2026) — 25 Q&As, All Levels
Vitest is now the go-to testing framework for modern JavaScript projects — especially anything built with Vite. If you have a frontend interview coming up, expect questions on it. This guide covers everything from the basics to tricky advanced topics, with clear answers and real code examples.
What is Vitest and why should you care?
Vitest is a testing framework built on top of Vite. Because it shares the same config, plugins, and module resolution as your app, it needs almost zero setup if you already use Vite (React, Vue, Svelte, Nuxt, Astro — they all work out of the box).
It was first released in late 2021 and by 2026 it has become the default choice for modern JavaScript and TypeScript projects. The main reasons developers love it: it's fast (watch mode re-runs only the tests affected by your change), it supports native ESM without wrestling with config flags, and its API is almost identical to Jest — so the learning curve is tiny.
Quick fact: Vitest uses the same
describe,it,expect, andvi.*API that Jest developers already know. Migrating from Jest is often a two-line config change.
Basic Questions
These cover what every developer should know before going into an interview. Even senior engineers get asked these as warm-up questions.
Q1 — What is Vitest and how is it different from Jest? Basic
Vitest is a unit testing framework that runs on top of Vite. The biggest difference from Jest is that it uses the same transformation pipeline as your Vite app, so you don't need a separate Babel or TypeScript config just for tests. It handles modern JavaScript features (native ESM, top-level await, etc.) without any extra flags.
Jest, on the other hand, was built before ES modules were standard. It works well but requires more setup to handle modern syntax — especially ESM — and runs tests in its own module environment that can differ from your actual app.
In terms of API they're almost identical. You use describe, it, expect, and mocking helpers just like in Jest. The main difference is you import from 'vitest' instead of '@jest/globals'.
Q2 — How do you set up Vitest in a project? Basic
Install it as a dev dependency, then add a test script to your package.json:
# Install
npm install -D vitest
# package.json scripts
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}
If you already have a vite.config.ts, you can add test options right inside it:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true, // use describe/it without imports
environment: 'jsdom', // for browser-like tests
coverage: {
provider: 'v8'
}
}
})
That's it. For a React project you'd also install @vitejs/plugin-react and jsdom.
Q3 — What does globals: true do in the Vitest config? Basic
By default, you need to import testing functions at the top of every test file:
import { describe, it, expect } from 'vitest'
With globals: true, these functions are automatically available everywhere without any import. This is handy for existing codebases being migrated from Jest, where globals were the default.
Tip: If you're starting a new project, it's fine to keep
globals: false(the default) and use explicit imports. It's more explicit and plays nicer with TypeScript autocompletion.
Q4 — What is watch mode and how does it work in Vitest? Basic
When you run vitest (without run), it starts in watch mode by default. It watches your files for changes and re-runs only the tests that are affected — similar to how Vite's Hot Module Replacement (HMR) works for the dev server.
This is much faster than running your full test suite on every save. If you change a utility function, only the tests that import it (directly or indirectly) get re-run, not all 200 tests in your project.
In CI environments where the CI environment variable is set, Vitest automatically switches to run mode, which runs all tests once and exits.
Q5 — How do you write a basic unit test in Vitest? Basic
// math.ts
export const add = (a: number, b: number) => a + b
export const divide = (a: number, b: number) => {
if (b === 0) throw new Error('Cannot divide by zero')
return a / b
}
// math.test.ts
import { describe, it, expect } from 'vitest'
import { add, divide } from './math'
describe('Math utils', () => {
it('adds two numbers', () => {
expect(add(2, 3)).toBe(5)
})
it('throws when dividing by zero', () => {
expect(() => divide(10, 0)).toThrow('Cannot divide by zero')
})
})
The structure is: describe groups related tests, it (or test) defines a single test case, and expect makes the assertion.
Q6 — What are beforeEach, afterEach, beforeAll, and afterAll? Basic
These are lifecycle hooks that run code around your tests:
beforeEach— runs before every single test in the describe block. Use this to reset state.afterEach— runs after every single test. Use this for cleanup (e.g.,vi.restoreAllMocks()).beforeAll— runs once before all tests in the block. Good for expensive setup like DB connections.afterAll— runs once after all tests. Good for cleanup like closing connections.
import { beforeEach, afterEach, describe, it, vi } from 'vitest'
describe('User service', () => {
beforeEach(() => {
// fresh state before each test
vi.useFakeTimers()
})
afterEach(() => {
// clean up after each test
vi.restoreAllMocks()
})
})
Q7 — How does Vitest handle TypeScript? Do you need Babel? Basic
No Babel needed. Vitest uses Vite's built-in esbuild transform to handle TypeScript. It strips types at lightning speed without needing ts-jest or any Babel configuration.
Just add "vitest/globals" to your tsconfig.json types array (or use the globals option) and TypeScript will understand your test types:
// tsconfig.json
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
Note: Vitest does not do full type-checking during test runs — it just strips types. For type-checking, run
tsc --noEmitseparately.
Mocking & Spying Questions
Mocking is where most people get tripped up in interviews. These questions test whether you really understand how Vitest controls dependencies in tests.
Q8 — What is the difference between vi.fn(), vi.spyOn(), and vi.mock()? Intermediate
vi.fn() creates a brand-new mock function from scratch. Use it when you need to pass a function as a prop or callback and want to track calls:
const onClick = vi.fn()
onClick('hello')
expect(onClick).toHaveBeenCalledWith('hello')
vi.spyOn() wraps an existing function on an object. The original function still runs unless you override it. Great for watching calls to real methods:
const spy = vi.spyOn(console, 'log')
console.log('test')
expect(spy).toHaveBeenCalledWith('test')
vi.mock() replaces an entire module. All named exports from that module become mock functions. This is for when you want to replace a whole dependency like an API client:
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Ada' })
}))
Q9 — Why does vi.mock() need to be at the top of the file? What is "hoisting"? Intermediate
ES module imports are resolved before any code runs. So if you try to call vi.mock('./api') after your imports, the real module has already been loaded — your mock arrives too late.
To fix this, Vitest hoists vi.mock() calls to the top of the file during code transformation. This means even if you write vi.mock() below your imports in the source, it actually runs first.
Common mistake: Because of hoisting, you can't reference variables defined in the test file inside
vi.mock()'s factory function (they haven't been initialized yet). Usevi.importMock()or inline the values directly.
Q10 — How do you mock a module that has both named and default exports? Intermediate
// If the module exports: export default myFn, export const helper = ...
vi.mock('./my-module', () => ({
default: vi.fn(), // mocks the default export
helper: vi.fn() // mocks the named export
}))
You can also use vi.importActual to keep the real implementation for some exports while mocking others:
vi.mock('./my-module', async () => {
const actual = await vi.importActual('./my-module')
return { ...actual, helper: vi.fn() } // only mock helper
})
Q11 — How do you mock timers like setTimeout in Vitest? Intermediate
Use vi.useFakeTimers() to replace setTimeout, setInterval, Date, and other timer globals with mock versions you can control:
import { vi, it, expect } from 'vitest'
it('debounces the call', () => {
vi.useFakeTimers()
const fn = vi.fn()
const debounced = debounce(fn, 300)
debounced()
debounced()
expect(fn).not.toHaveBeenCalled() // not fired yet
vi.advanceTimersByTime(300)
expect(fn).toHaveBeenCalledOnce() // now it fires once
vi.restoreAllMocks()
})
You can also set the current date with vi.setSystemTime(new Date('2026-01-01')) for predictable date-based tests.
Q12 — How do you test async code and API calls in Vitest? Intermediate
Use async/await — Vitest handles promises natively. For mocking API calls, use vi.fn().mockResolvedValue():
import { vi, it, expect } from 'vitest'
import { getUser } from './api'
vi.mock('./api')
it('loads and shows user data', async () => {
getUser.mockResolvedValue({ name: 'Ada', role: 'admin' })
const result = await getUser(42)
expect(result.name).toBe('Ada')
})
it('handles API errors', async () => {
getUser.mockRejectedValue(new Error('Network error'))
await expect(getUser(99)).rejects.toThrow('Network error')
})
Q13 — What is the difference between vi.clearAllMocks(), vi.resetAllMocks(), and vi.restoreAllMocks()? Intermediate
clearAllMocks()— clears call history, instances, and results, but keeps the mock implementation. Useful between tests when you want to reset call counts but keep the mock behavior.resetAllMocks()— does everythingcleardoes, plus removes the mock implementation. The mock still exists but now returnsundefined.restoreAllMocks()— does everythingresetdoes, plus restores functions that were spied on withvi.spyOn()back to their original implementations. This is the most complete cleanup — use it inafterEach.
Advanced Questions
Q14 — What are snapshot tests and when should you use them? Advanced
A snapshot test captures the output of a function (usually a rendered component) and saves it to a .snap file. On future test runs, it compares the current output against the saved snapshot and fails if they differ.
it('renders the button correctly', () => {
const result = render(<Button label="Save" />)
expect(result).toMatchSnapshot()
})
The first time you run this test, Vitest creates the snapshot file. If you later change the component's output, the test fails — then you decide: was this change intentional? If yes, run vitest --update (or press u in watch mode) to accept the new snapshot.
When to use them: Good for stable output like serialized data, rendered HTML structures, or UI components that don't change often. Bad for things that change frequently — constant snapshot updates become noise and lose their value.
Vitest also supports inline snapshots with toMatchInlineSnapshot() which stores the snapshot directly in the test file, which is handy for small values.
Q15 — How do you set up code coverage in Vitest? Advanced
Vitest supports two coverage providers: v8 (uses Node's built-in V8 engine, very fast) and istanbul (more detailed and widely supported).
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'json'],
thresholds: {
lines: 80,
branches: 75,
functions: 80,
statements: 80
}
}
}
})
Run it with vitest run --coverage. The thresholds option makes your tests fail if coverage drops below the set percentages — useful for CI pipelines.
Tip: Don't chase 100% coverage. Focus on covering your business logic, edge cases, and error paths. Trivial getters and framework boilerplate don't need testing.
Q16 — What is Vitest Browser Mode and when would you use it? Advanced
Browser Mode runs your tests in a real browser (via Playwright or WebdriverIO) instead of a simulated DOM environment like jsdom. This means your tests run in the same environment your users see.
Use it when you need to test things that jsdom doesn't implement well — CSS layout, scroll behavior, canvas, Web APIs like ResizeObserver, or when you need pixel-perfect visual regression tests.
// vitest.config.ts
{
test: {
browser: {
enabled: true,
name: 'chromium',
provider: 'playwright'
}
}
}
With browser mode you also get access to visual regression testing via toMatchScreenshot(), which captures screenshots and compares them against saved reference images:
import { expect, test } from 'vitest'
import { page } from 'vitest/browser'
test('hero section looks right', async () => {
await expect(page.getByTestId('hero')).toMatchScreenshot('hero-section')
})
Q17 — How do you test React components with Vitest? Advanced
You pair Vitest with React Testing Library. Install @testing-library/react and @testing-library/jest-dom, set your environment to jsdom, and you're ready:
// Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { Counter } from './Counter'
describe('Counter', () => {
it('shows the initial count', () => {
render(<Counter initialCount={5} />)
expect(screen.getByText('Count: 5')).toBeInTheDocument()
})
it('increments on click', () => {
render(<Counter initialCount={0} />)
fireEvent.click(screen.getByRole('button', { name: '+' }))
expect(screen.getByText('Count: 1')).toBeInTheDocument()
})
})
The key philosophy: test what the user sees, not the implementation. Use getByRole, getByText, and getByLabelText — not class names or component internals.
Q18 — What is Vitest UI and when is it useful? Advanced
Vitest UI is a browser-based visual interface for running and inspecting tests. Run it with vitest --ui and it opens a dashboard where you can:
- See all your tests organized by file and describe block
- Filter by name, file, or status (passed/failed/skipped)
- Click to re-run individual tests without restarting
- View console output, error stack traces, and test duration inline
- Update snapshots with a single click
It's most useful when debugging a specific failing test in a large codebase — much nicer than scrolling through terminal output.
Q19 — How do you run tests in parallel and use sharding in Vitest? Advanced
By default, Vitest runs test files in parallel across multiple worker threads. You can control this with pool and poolOptions in the config.
For large test suites in CI, sharding lets you split tests across multiple machines:
# Machine 1: run first half
vitest run --shard=1/2
# Machine 2: run second half
vitest run --shard=2/2
After sharding, merge the coverage reports with:
vitest --merge-reports --reporter=junit --coverage
Q20 — How does Vitest work in a monorepo (workspace setup)? Advanced
Vitest supports a workspace mode where you define multiple projects in a single config. Each project can have its own environment, plugins, and settings:
// vitest.workspace.ts
export default [
'packages/*/vitest.config.ts', // glob pattern
{
test: { name: 'node-utils', environment: 'node' }
},
{
test: { name: 'react-app', environment: 'jsdom' }
}
]
This lets you run all packages in your monorepo with a single vitest command while still giving each package its own isolated test config.
Tricky "Gotcha" Questions
These are the questions that trip people up — even experienced developers. They test deep understanding, not just surface knowledge.
Q21 — Your vi.mock() doesn't seem to work — the real function still runs. What could be wrong? Advanced
A few common causes:
- Wrong path: The path in
vi.mock('./api')must exactly match the path used in the file being tested, including relative vs absolute paths. - Direct import binding: If the file under test does
import { fetchUser } from './api'and then callsfetchUser()directly, mocking works fine. But if it binds to a local variable first, watch out — ES module bindings are live. - Factory function not returning the mock: If your
vi.mock()factory doesn't explicitly return a mock for the export, the export will beundefined, not a mock function. - Not calling
vi.mock()at the module top level: It needs to be a top-level call, not inside a function or anifblock.
Q22 — What happens if you don't call vi.restoreAllMocks() after using vi.spyOn()? Advanced
The spy will stay active across tests. This means if you spy on console.error in one test and don't restore it, the next test's console.error will still be the spy — not the real function. This causes test pollution: tests start depending on each other's setup, making failures hard to reproduce.
Always restore in afterEach, or configure Vitest to do it automatically:
// vitest.config.ts
{
test: {
restoreMocks: true // restore all spies after each test
}
}
Q23 — When should you not use snapshot tests? Advanced
Snapshots become a liability when:
- The output changes often — if you're updating snapshots every PR, they're just noise, not a safety net.
- The snapshot is huge — snapshotting 500 lines of HTML means nobody will actually review the diff when it changes.
- You're testing the wrong thing — snapshots of a whole component tree test implementation details. Better to assert specific text content or behavior.
- Dates, IDs, or random values are in the output — these change every run and break the snapshot. Either mock them or exclude them from the snapshot.
Q24 — What is the difference between it.only, it.skip, and it.todo? Intermediate
it.only(ortest.only) — runs ONLY this test (and any others marked.only) in the file. Everything else is skipped. Useful for debugging a single failing test. Never commit this to main!it.skip— marks a test as skipped. It won't run, but it shows up as "skipped" in the output rather than being silently absent.it.todo— a placeholder for a test you plan to write. It shows in the output as a reminder. Unlike.skip, you don't even provide a test function.
it.only('runs just this one', () => { ... })
it.skip('temporarily skipped', () => { ... })
it.todo('write this test later')
Q25 — How would you migrate a project from Jest to Vitest? Advanced
The migration is usually straightforward because the APIs are nearly identical. Here's the basic process:
- Install Vitest:
npm install -D vitest - Replace
jest.config.jswithvitest.config.ts(or add atestkey to your existingvite.config.ts) - Do a find-and-replace:
jest.fn()→vi.fn(),jest.spyOn()→vi.spyOn(),jest.mock()→vi.mock() - If you use
@testing-library/jest-dom, add a setup file that imports it - Remove
jest,ts-jest, andbabel-jestfrom your dependencies
For most projects this takes less than an hour. The main edge cases are tests that rely on Jest's module registry internals or use Jest-specific reporters.
Vitest vs Jest: Quick Comparison
| Feature | Vitest | Jest |
|---|---|---|
| Setup speed | ✅ Zero config with Vite | Requires Babel or ts-jest |
| ESM support | ✅ Native, first-class | Experimental flag needed |
| Watch mode | ✅ Smart HMR-like reruns | Full re-run or pattern match |
| TypeScript | ✅ Built-in via esbuild | Needs ts-jest or babel |
| API | ✅ Jest-compatible (vi.* = jest.*) | Established, large ecosystem |
| Browser mode | ✅ Built-in (Playwright) | ❌ Not available |
| UI dashboard | ✅ Built-in (vitest --ui) | ❌ Not built-in |
| Maturity / ecosystem | Growing fast | ✅ Larger, more plugins |
| Non-Vite projects | Works but needs config | ✅ Framework-agnostic |
Final Tips Before Your Interview
Know the "why", not just the "what". Interviewers don't just want to hear what a function does — they want to know when and why you'd use it. For example, don't just say "vi.spyOn wraps a function." Say "I use vi.spyOn when I want to observe calls to a real method without fully replacing the module."
Talk about mistakes you've made. Being able to say "I once forgot to restore a spy and my tests started randomly failing — here's what I learned" shows real experience. Interviewers trust that kind of answer.
Know the testing philosophy. The biggest principle behind Vitest and React Testing Library is: test behavior, not implementation. Don't test that a private function was called. Test that the right output appears, the right side effect happens, or the right error is thrown.
Be honest about the tradeoffs. Vitest is excellent for Vite projects but may be overkill for a small Node.js script. Jest still has a larger ecosystem for certain use cases. Showing that you know when not to use something is a green flag to most interviewers.
Good luck with your interview! Read through the official Vitest docs at vitest.dev for the latest features — the framework moves fast.