Mastering Mocking in Vitest: Beyond
Master Vitest mocking! Learn advanced techniques beyond vi.mock() for robust, reliable JavaScript/TypeScript tests. SpyOn, vi.fn, and more!

Introduction: The Why and When of Mocking
Setting the Stage: Why Mocking Matters
Imagine you're building a website for the Trade Federation, those folks from Star Wars. They're selling goods to both the Empire and the Rebels. Now, their website has a complex system for processing orders. It talks to a database, sends emails, and even checks stock levels with a remote API.
Here's the problem: when you want to test just one part of this system, say, the order processing logic, you don't want to rely on the database, the email service, or the external API. Why?
- Slow Tests: Talking to real external services takes time. Your tests will be slow, and you'll be waiting a long time to see if your code works.
- Unreliable Tests: If the database or API is down, your tests will fail, even if your code is perfect. This is called "flakiness," and it's a real headache.
- Side Effects: Sending real emails or changing a real database can have unwanted side effects. You don't want to accidentally send a thousand emails to the Empire while testing!
That's where mocking comes in. Mocking means creating fake versions of these external dependencies. We replace the real database with a fake one, the real email service with a fake one, and so on. This isolates the part of your code you're testing, so you can focus on it without distractions.
Example:
// OrderProcessor.ts
import { Database } from './database';
import { EmailService } from './emailService';
import { StockAPI } from './stockAPI';
export class OrderProcessor {
constructor(
private database: Database,
private emailService: EmailService,
private stockAPI: StockAPI
) {}
async processOrder(order: any) {
await this.stockAPI.checkStock(order.productId);
await this.database.saveOrder(order);
await this.emailService.sendConfirmation(order.customerEmail);
return 'Order processed';
}
}
When testing OrderProcessor
, we don't want to rely on real Database
, EmailService
, or StockAPI
. We'll mock them!
The Pitfalls of Over-Mocking: Don't Lose Sight of Reality
Mocking is powerful, but it's easy to go overboard. If you mock everything, your tests might not reflect how your code works in the real world.
Imagine you mock the entire order processing system, including the business logic. Your tests might pass, but if there's a bug in the real logic, you won't catch it.
It's about finding the right balance. Mock external dependencies, but don't mock the core logic of the unit you're testing unless it is absolutely necessary.
Think of it this way: you want to test if the Trade Federation's order processor works correctly. But you don't want to test if the entire galaxy is working correctly.
Example:
If you mock the processOrder
function itself, you will not test the logic inside of it, and the test is useless.
// OrderProcessor.test.ts
// Don't do this (unless you have a very good reason!)
vi.mock('./OrderProcessor', () => ({
OrderProcessor: vi.fn().mockImplementation(() => ({
processOrder: vi.fn().mockResolvedValue('Mocked order processed'),
})),
}));
Introducing Fixtures: Setting Up a Consistent Stage
In the world of testing, a "fixture" is like a stage set. It's a way to create a consistent starting point for your tests.
Imagine you're testing the Trade Federation's inventory system. You need to create some sample products to test with. Instead of creating these products in every test, you can create a fixture.
Example: Object Fixture
// __fixtures__/product.fixtures.ts
export const productFixture = {
productId: '123',
name: 'Droid Parts',
price: 100,
};
Then, in your test:
// discount.test.ts
import { expect, test } from 'vitest';
import { productFixture } from './__fixtures__/product.fixtures';
import { applyDiscount } from './discount';
test('should apply a discount to a product price', () => {
// Arrange
const discountedPrice = applyDiscount(productFixture.price, 20);
// Assert
expect(discountedPrice).toBe(80);
});
Example: Function Fixture
You can also use functions to create more complex fixtures:
// __fixtures__/order.fixtures.ts
export const createOrderFixture = (productId: string, quantity: number) => ({
orderId: 'order-66',
productId,
quantity,
customerEmail: 'rebel@alliance.com',
});
And in your test:
// order.test.ts
import { expect, test } from 'vitest';
import { createOrderFixture } from './__fixtures__/order.fixtures.ts';
import { productFixture } from './__fixtures__/product.fixtures.ts';
import { calculateTotal } from './order.ts';
test('should calculate the total price of an order', () => {
// Arrange
const order = createOrderFixture('123', 5);
const total = calculateTotal(order.quantity, productFixture.price);
// Assert
expect(total).toBe(500);
});
Using fixtures makes your tests:
- DRY (Don't Repeat Yourself): You avoid repeating the same setup code.
- Readable: Your tests are cleaner and easier to understand.
- Maintainable: If you need to change the setup, you only change it in one place.
Core Mocking Techniques: A Deep Dive
`vi.spyOn()`: Being a Code Detective (and Letting the Suspect Act)
Imagine you need to keep tabs on a function, but you don't want to stop it from doing its job. That's the beauty of vi.spyOn()
. It's like placing a wiretap—you're listening in, but the conversation keeps going. You can see what a function is doing, how many times it's called, and what arguments it receives, all while letting it execute its actual logic.
Let's say the Trade Federation has a ShipmentProcessor
class:
// shipment.ts
export class ShipmentProcessor {
sendShipment(orderId: string, address: string) {
console.log(`Sending shipment ${orderId} to ${address}`);
// ... some real shipping logic ...
return `Shipment ${orderId} sent`;
}
}
Here's how you'd spy on the sendShipment
method:
// shipment.test.ts
import { vi, expect, test } from 'vitest';
import { ShipmentProcessor } from './shipment';
test('should call sendShipment with correct arguments and return the correct value', () => {
// Arrange
const processor = new ShipmentProcessor();
const spy = vi.spyOn(processor, 'sendShipment');
const orderId = 'order-66';
const address = 'Coruscant';
// Act
const result = processor.sendShipment(orderId, address);
// Assert
expect(spy).toHaveBeenCalledWith(orderId, address);
expect(spy).toHaveBeenCalledTimes(1);
expect(result).toBe(`Shipment ${orderId} sent`); // Check the returned value
spy.mockRestore(); // Clean up the spy!
});
Notice how we're also checking the returned value of sendShipment
. This proves that the actual function logic is executed. Remember, vi.spyOn()
lets the function run its course while you observe.
**`vi.fn()`****: Building Your Own Fake Functions (With Care)**
Now, sometimes you need to completely replace a function with a fake one. That's where vi.fn()
comes in. It's like building a robot that mimics a real person. You get to decide exactly what the function returns, what errors it throws, or what logic it runs.
But here's the catch: you need to be careful. If your fake function doesn't behave similarly to the real one, your tests might not be relevant. You need to mimic the real function's behavior to some extent.
Let's look at an example with a database:
// database.ts
export class Database {
async saveOrder(data: any) {
// ... real database logic ...
return 'Data saved';
}
}
// OrderProcessor.ts
import { Database } from './database';
import { EmailService } from './emailService';
import { StockAPI } from './stockAPI';
export class OrderProcessor {
constructor(
private database: Database,
private emailService: EmailService,
private stockAPI: StockAPI
) {}
async processOrder(order: any) {
await this.stockAPI.checkStock(order.productId);
await this.database.saveOrder(order);
await this.emailService.sendConfirmation(order.customerEmail);
return 'Order processed';
}
}
// orderProcessor.test.ts
import { createOrderFixture } from './__fixtures__/order.fixtures';
import { OrderProcessor } from './orderProcessor';
import { vi, expect, test } from 'vitest';
test('should save order to database', async () => {
// Arrange
const mockDatabase = { saveOrder: vi.fn() };
const mockEmailService = { sendConfirmation: vi.fn() };
const mockStockAPI = { checkStock: vi.fn() };
const processor = new OrderProcessor(mockDatabase, mockEmailService, mockStockAPI);
const order = createOrderFixture('123', 5);
// Act
await processor.processOrder(order);
// Assert
expect(mockDatabase.saveOrder).toHaveBeenCalledWith(order);
});
test('should use mockImplementationOnce to control the return value of a function for a single call', () => {
// Arrange
const mockFunction = vi.fn();
mockFunction.mockImplementationOnce(()=> 'first call');
mockFunction.mockImplementationOnce(()=> 'second call');
// Act / Assert
expect(mockFunction()).toBe('first call');
expect(mockFunction()).toBe('second call');
expect(mockFunction()).toBe(undefined);
})
test('should use mockReturnValueOnce to control the return value of a function for a single call', () => {
// Arrange
const mockFunction = vi.fn();
mockFunction.mockReturnValueOnce('first call');
mockFunction.mockReturnValueOnce('second call');
// Act / Assert
expect(mockFunction()).toBe('first call');
expect(mockFunction()).toBe('second call');
expect(mockFunction()).toBe(undefined);
})
**Manual Mocks: Taking Full Control (with** **`vi.fn()`****)**
When vi.mock()
and vi.fn()
aren't enough, you need to create a completely custom mock for a module. This is where manual mocks come in, and you can even use vi.fn()
within them.
Let's say you have a module that reads data from a file:
// fileReader.ts
import * as fs from 'fs';
export function readData(filePath: string) {
return fs.readFileSync(filePath, 'utf-8');
}
Create a __mocks__
directory and a fileReader.ts
file inside:
// __mocks__/fileReader.ts
import { vi } from 'vitest';
export const readData = vi.fn((filePath: string) => {
if (filePath === 'test.txt') {
return 'Mocked data';
}
return '';
});
Now, in your test:
// fileReader.test.ts
import { readData } from './fileReader';
import { vi, expect, test } from 'vitest';
// This line will automatically replace every import of 'fileReader' with the mocked version
vi.mock('./fileReader');
test('should use mocked file data', () => {
// Arrange / Act
const data = readData('test.txt');
// Assert
expect(data).toBe('Mocked data');
expect(readData).toHaveBeenCalledWith('test.txt');
expect(readData).toHaveBeenCalledTimes(1);
});
Using vi.fn()
in your manual mock gives you the ability to check call counts and arguments, adding even more control and precision to your tests.
Advanced Mocking Scenarios
Mocking Asynchronous Functions and Promises: Taming the Async Beast
In the world of JavaScript, asynchronous code is everywhere. Dealing with promises and async/await
in tests can be tricky, but Vitest provides powerful tools to make it easier.
Mocking with `mockResolvedValue()` and `mockRejectedValue()`
Imagine you have a function that fetches data from an API:
// apiService.ts
export class ApiService {
async fetchData(url: string) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
}
Here's how you'd mock it:
// apiService.test.ts
import { ApiService } from './apiService';
import { vi, expect, test } from 'vitest';
test('should resolve with data', async () => {
// Arrange
const apiService = new ApiService();
const fetchDataSpy = vi.spyOn(apiService, 'fetchData');
fetchDataSpy.mockResolvedValue({ message: 'Data received' });
// Act
const data = await apiService.fetchData('https://example.com/data');
// Assert
expect(data).toEqual({ message: 'Data received' });
expect(fetchDataSpy).toHaveBeenCalledWith('https://example.com/data');
});
test('should reject with an error', async () => {
// Arrange
const apiService = new ApiService();
const fetchDataSpy = vi.spyOn(apiService, 'fetchData');
fetchDataSpy.mockRejectedValue(new Error('Network error'));
// Act / Assert
await expect(apiService.fetchData('https://example.com/data')).rejects.toThrow('Network error');
expect(fetchDataSpy).toHaveBeenCalledWith('https://example.com/data');
});
Complex Asynchronous Mocks with `mockImplementation(async () => {...})`
For more complex scenarios, you can use mockImplementation
with an async
function:
test('should handle complex async logic', async () => {
// Arrange
const apiService = new ApiService();
const mockFetchData = vi.spyOn(apiService, 'fetchData').mockImplementation(async (url: string) => {
if (url === 'https://example.com/special') {
return { special: true };
}
return { normal: true };
});
// Act
const specialData = await apiService.fetchData('https://example.com/special');
const normalData = await apiService.fetchData('https://example.com/normal');
// Assert
expect(specialData).toEqual({ special: true });
expect(normalData).toEqual({ normal: true });
mockFetchData.mockRestore();
});
Mocking External APIs: Building a Fake Galaxy
When your code interacts with external APIs, you need to mock those calls to keep your tests fast and reliable.
Using `fetch` Mocks
As shown in the previous examples, mocking fetch
is a common way to handle API calls. You can control the responses and simulate different scenarios.
Using Mock Service Worker (MSW)
For more complex API mocking, consider using MSW. It lets you intercept network requests at the network level, providing a realistic mocking experience.
// __msw__/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('https://api.example.com/products', () => {
return HttpResponse.json([{ id: 1, name: 'Droid Parts' }]);
}),
http.post('https://api.example.com/orders', () => {
return HttpResponse.json({ orderId: 'order-66' });
}),
];
// api.test.ts
import { setupServer } from 'msw/node';
import { afterAll, afterEach, beforeAll, expect, test } from 'vitest';
import { handlers } from './__msw__/handlers';
import { fetchProducts, createOrder } from './api.ts';
const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test('should fetch products', async () => {
const products = await fetchProducts();
expect(products).toEqual([{ id: 1, name: 'Droid Parts' }]);
});
test('should create an order', async () => {
const order = await createOrder({ productId: 1, quantity: 2 });
expect(order).toEqual({ orderId: 'order-66' });
});
Mocking Browser APIs in Node.js: Bringing the Browser to Node
Sometimes, your code might run in a Node.js environment but still rely on browser-specific APIs like window
or localStorage
. This is common in frameworks that support server-side rendering or when you're using libraries that make use of browser globals. In these cases, you need to mock those APIs to ensure your tests run correctly in Node.js.
Using `vi.stubGlobal()`
vi.stubGlobal()
lets you replace global objects with mock values. This is useful for simple mocks. Let's create a Star Wars example:
// planetStorage.ts
export function saveLastVisitedPlanet(planet: string) {
localStorage.setItem('lastVisitedPlanet', planet);
}
export function getLastVisitedPlanet() {
return localStorage.getItem('lastVisitedPlanet');
}
// planetStorage.test.ts
import { saveLastVisitedPlanet, getLastVisitedPlanet } from './planetStorage';
import { vi, expect, test } from 'vitest';
test('should mock localStorage for planet storage', () => {
// Arrange
const mockLocalStorage = {
store: {},
setItem: vi.fn((key, value) => (mockLocalStorage.store[key] = value)),
getItem: vi.fn((key) => mockLocalStorage.store[key]),
};
vi.stubGlobal('localStorage', mockLocalStorage);
// Act
saveLastVisitedPlanet('Tatooine');
const lastPlanet = getLastVisitedPlanet();
// Assert
expect(mockLocalStorage.setItem).toHaveBeenCalledWith('lastVisitedPlanet', 'Tatooine');
expect(lastPlanet).toBe('Tatooine');
});
Using `jsdom`
For more complex browser environments, use jsdom
. It creates a simulated browser environment in Node.js, allowing you to test code that relies on the document
object or other browser APIs.
// documentUtil.ts
export function getTitle() {
return document.title;
}
// documentUtil.test.ts
import { expect, test, beforeAll } from 'vitest';
import { JSDOM } from 'jsdom';
import { getTitle } from './documentUtil';
beforeAll(() => {
const dom = new JSDOM('<!DOCTYPE html><title>Trade Federation Page</title>');
global.document = dom.window.document;
});
test('should mock document.title', () => {
expect(getTitle()).toBe('Trade Federation Page');
});
Mocking Classes: Shaping the Object World
Mocking classes is essential when your code depends on complex objects. It allows you to isolate the unit under test and control the behavior of its dependencies.
Mocking Class Methods
Use vi.spyOn()
or vi.fn()
to mock class methods. This is useful when you want to observe or modify the behavior of specific methods.
// droid.ts
export class Droid {
speak() {
return 'Beep boop';
}
}
// droid.test.ts
import { Droid } from './droid';
import { vi, expect, test } from 'vitest';
test('should mock droid speak', () => {
const droid = new Droid();
const speakSpy = vi.spyOn(droid, 'speak').mockReturnValue('Mocked beep');
expect(droid.speak()).toBe('Mocked beep');
expect(speakSpy).toHaveBeenCalled();
});
Mocking Class Constructors
Use vi.mock()
to mock class constructors. This is useful when you want to replace the entire class with a mock implementation.
// communicator.ts
import { Droid } from "./droid";
export class Communicator {
constructor(private droid: Droid) {}
communicate() {
return this.droid.speak();
}
}
// communicator.test.ts
import { Communicator } from './communicator';
import { Droid } from './droid';
import { vi, expect, test, Mock } from 'vitest';
vi.mock('./droid');
test('should mock droid constructor', () => {
vi.mocked(Droid).mockImplementation(() => ({
speak: vi.fn().mockReturnValue('Mocked constructor beep'),
}));
const communicator = new Communicator(new Droid());
expect(communicator.communicate()).toBe('Mocked constructor beep');
});
Verification and Assertion Techniques: Proving Your Tests Right
Mocking is only half the battle. Once you've set up your mocks, you need to verify that they're being used correctly. This is where Vitest's assertion techniques come in. Let's dive into how to use them effectively.
`toHaveBeenCalledTimes()`: Ensuring Function Calls and Catching Unexpected Behavior
Verifying that a function was called, and how many times, is crucial. It ensures that your code is executing the expected logic. While toHaveBeenCalled()
simply checks if a function was called at least once, toHaveBeenCalledTimes()
provides a more precise verification.
Imagine you have a DroidCommunicator
that sends messages:
// droidCommunicator.ts
export class DroidCommunicator {
sendMessage(message: string, droidId: string) {
console.log(`Sending message "${message}" to droid ${droidId}`);
// ... actual communication logic ...
}
}
Here's how you'd verify the function calls:
// droidCommunicator.test.ts
import { DroidCommunicator } from './droidCommunicator';
import { vi, expect, test } from 'vitest';
test('should call sendMessage exactly once', () => {
// Arrange
const communicator = new DroidCommunicator();
const sendMessageSpy = vi.spyOn(communicator, 'sendMessage');
// Act
communicator.sendMessage('Hello there!', 'R2-D2');
// Assert
expect(sendMessageSpy).toHaveBeenCalledTimes(1);
});
test('should call sendMessage multiple times', () => {
// Arrange
const communicator = new DroidCommunicator();
const sendMessageSpy = vi.spyOn(communicator, 'sendMessage');
// Act
communicator.sendMessage('Message 1', 'C-3PO');
communicator.sendMessage('Message 2', 'C-3PO');
// Assert
expect(sendMessageSpy).toHaveBeenCalledTimes(2);
});
Now, let's explore a situation where toHaveBeenCalledTimes()
helps you catch unexpected behavior. Suppose your DroidCommunicator
has a feature where it should only send a message once, even if the user tries to send it multiple times in quick succession:
// droidCommunicator.ts
export class DroidCommunicator {
private messageSent = false;
constructor(private logger: (text: string) => void) {}
sendMessage(message: string, droidId: string) {
if (!this.messageSent) {
this.logger(`Sending message "${message}" to droid ${droidId}`);
// ... actual communication logic ...
this.messageSent = true;
}
}
}
test('should only send message once, even if called multiple times', () => {
// Arrange
const logger = vi.fn();
const communicator = new DroidCommunicator(logger);
// Act
communicator.sendMessage('Urgent Message', 'BB-8');
communicator.sendMessage('Urgent Message', 'BB-8');
communicator.sendMessage('Urgent Message', 'BB-8');
// Assert
expect(logger).toHaveBeenCalledTimes(1);
});
In this example, toHaveBeenCalledTimes(1)
ensures that the message is only sent once, even though sendMessage
is called multiple times. This helps catch potential bugs and improves the robustness of your code by preventing unintended side effects.
`toHaveBeenCalledWith()` and `toHaveBeenLastCalledWith()`: Checking Arguments
Verifying the arguments passed to mock functions is essential to ensure that your code is passing the correct data. toHaveBeenCalledWith()
checks if the mocked function was called with the specific arguments at least once, while toHaveBeenLastCalledWith()
checks the arguments of the last call.
test('should call sendMessage with specific arguments', () => {
// Arrange
const communicator = new DroidCommunicator(console.log);
const sendMessageSpy = vi.spyOn(communicator, 'sendMessage');
// Act
communicator.sendMessage('May the Force be with you', 'BB-8');
// Assert
expect(sendMessageSpy).toHaveBeenCalledWith('May the Force be with you', 'BB-8');
});
test('should check the last call arguments', () => {
// Arrange
const communicator = new DroidCommunicator(console.log);
const sendMessageSpy = vi.spyOn(communicator, 'sendMessage');
// Act
communicator.sendMessage('Message 1', 'C-3PO');
communicator.sendMessage('Message 2', 'R2-D2');
// Assert
expect(sendMessageSpy).toHaveBeenLastCalledWith('Message 2', 'R2-D2');
});
Why Check Arguments?
Checking arguments is crucial because it ensures that your mocks are being called with the correct data. Without this, your tests might pass, but your code might still have bugs. For example, if you're mocking an API call, you need to make sure that the correct parameters are being sent to the API.
Custom Matchers (Optional): Extending Vitest's Assertions
For more complex assertions, you can create custom matchers. This is useful when you have specific assertion logic that you want to reuse across multiple tests.
Here's a simple example:
// __helpers__/customMatchers.ts
import { expect } from 'vitest';
expect.extend({
toBeDroidId(received, expected) {
const pass = received.startsWith('C');
if (pass) {
return {
message: () => `expected ${received} not to be a droid ID`,
pass: true,
};
} else {
return {
message: () => `expected ${received} to be a droid ID`,
pass: false,
};
}
},
});
// vitest.d.ts
import 'vitest';
interface CustomMatchers<R = unknown> {
toBeDroidId: () => R
}
declare module 'vitest' {
interface Assertion<T = any> extends CustomMatchers<T> {}
interface AsymmetricMatchersContaining extends CustomMatchers {}
}
// droidCommunicator.test.ts
import './customMatchers'; // Import the custom matchers
import { expect, test } from 'vitest';
test('should use custom matcher', () => {
expect('D123').toBeDroidId();
expect('C3PO').not.toBeDroidId();
});
Explanation:
toHaveBeenCalledTimes(number)
: Verifies that a mock function has been called a specific number of times, allowing you to catch unexpected behavior.toHaveBeenCalledWith(...args)
: Verifies that a mock function has been called with specific arguments.toHaveBeenLastCalledWith(...args)
: Verifies that the last call to a mock function was with specific arguments.- Custom matchers: Allow you to define your own assertion logic for more complex scenarios.
Best Practices and Conclusion: Crafting Maintainable and Reliable Tests
Keep Mocks Focused: Mock Only What You Must
One of the most common pitfalls in testing is over-mocking. It's tempting to mock everything, but this can lead to tests that are disconnected from the actual behavior of your code. Remember, the goal of mocking is to isolate the unit under test, not to replace every dependency.
Only mock external dependencies or parts of your code that are difficult to test directly. For example, mock API calls, database interactions, or complex third-party libraries. But avoid mocking the core logic of the unit you're testing.
Ask yourself, "What am I trying to test?" If the answer involves the interaction with an external system or a complex dependency, then mocking is appropriate. Otherwise, try to test the code as it is.
Maintain Test Readability: Clarity is Key
Tests should be as readable as possible. This makes them easier to understand, maintain, and debug. Here are a few tips:
- Use descriptive mock names: Instead of
mockFn
, use names likemockApiService
ormockDatabaseSave
. - Add comments: Explain why you're mocking a particular dependency or what a specific assertion is checking.
- Keep tests concise: Break down complex tests into smaller, more focused ones.
- Follow the Arrange-Act-Assert pattern: This makes your tests easier to follow.
Summary: Empowering Your Testing Skills
In this guide, we've explored advanced mocking techniques in Vitest, going beyond the basics to tackle complex scenarios. We've covered:
- The purpose and benefits of mocking.
- Core mocking techniques like
vi.spyOn()
,vi.fn()
, and manual mocks. - Advanced mocking scenarios, including asynchronous functions, external APIs, browser APIs, and classes.
- Verification and assertion techniques to ensure your mocks are used correctly.
- Fixtures to keep your tests DRY.
By mastering these techniques, you can write more robust, reliable, and maintainable tests. Remember to mock only what's necessary, keep your tests readable, and always verify your mocks with appropriate assertions.
Further Resources:
- Vitest Documentation: https://vitest.dev/
- Mock Service Worker (MSW): https://mswjs.io/
- JSDOM: https://github.com/jsdom/jsdom
I encourage you to apply these techniques in your own projects and continue to explore the power of Vitest. Happy testing!
Vitest Mocking Cheatsheet
Core Mocks
vi.spyOn(obj, method)
: Spy on a method, keep original.vi.fn(impl?)
: Create a mock function.vi.mock(path, factory?)
: Mock a module.__mocks__/module.ts
: Manual mock.
Mock Behavior
mockFn.mockImplementationOnce(fn)
: Set mock function behavior once.mockFn.mockReturnValueOnce(val)
: Set return value once.mockFn.mockResolvedValueOnce(val)
: Set resolved promise value once.mockFn.mockRejectValueOnce(err)
: Set rejected promise value.spy.mockRestore()
: Restore original method.
Assertions
expect(mock).toHaveBeenCalledTimes(n)
: Check call count.expect(mock).toHaveBeenCalledWith(...args)
: Check call args.expect(mock).toHaveBeenLastCalledWith(...args)
: Check last call args.
Browser/API
vi.stubGlobal(name, val)
: Mock global object.- jsdom: Browser env in Node.
- MSW: API mocking (
setupServer
,rest.get/post
).