chore: consolidate new foundation and archive v1 (#1495)
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
---
|
||||
name: mocking
|
||||
description: Mock functions, modules, timers, and dates with vi utilities
|
||||
---
|
||||
|
||||
# Mocking
|
||||
|
||||
## Mock Functions
|
||||
|
||||
```ts
|
||||
import { expect, vi } from 'vitest';
|
||||
|
||||
// Create mock function
|
||||
const fn = vi.fn();
|
||||
fn('hello');
|
||||
|
||||
expect(fn).toHaveBeenCalled();
|
||||
expect(fn).toHaveBeenCalledWith('hello');
|
||||
|
||||
// With implementation
|
||||
const add = vi.fn((a, b) => a + b);
|
||||
expect(add(1, 2)).toBe(3);
|
||||
|
||||
// Mock return values
|
||||
fn.mockReturnValue(42);
|
||||
fn.mockReturnValueOnce(1).mockReturnValueOnce(2);
|
||||
fn.mockResolvedValue({ data: true });
|
||||
fn.mockRejectedValue(new Error('fail'));
|
||||
|
||||
// Mock implementation
|
||||
fn.mockImplementation((x) => x * 2);
|
||||
fn.mockImplementationOnce(() => 'first call');
|
||||
```
|
||||
|
||||
## Spying on Objects
|
||||
|
||||
```ts
|
||||
const cart = {
|
||||
getTotal: () => 100,
|
||||
};
|
||||
|
||||
const spy = vi.spyOn(cart, 'getTotal');
|
||||
cart.getTotal();
|
||||
|
||||
expect(spy).toHaveBeenCalled();
|
||||
|
||||
// Mock implementation
|
||||
spy.mockReturnValue(200);
|
||||
expect(cart.getTotal()).toBe(200);
|
||||
|
||||
// Restore original
|
||||
spy.mockRestore();
|
||||
```
|
||||
|
||||
## Module Mocking
|
||||
|
||||
```ts
|
||||
// vi.mock is hoisted to top of file
|
||||
vi.mock('./api', () => ({
|
||||
fetchUser: vi.fn(() => ({ id: 1, name: 'Mock' })),
|
||||
}));
|
||||
|
||||
import { fetchUser } from './api';
|
||||
|
||||
test('mocked module', () => {
|
||||
expect(fetchUser()).toEqual({ id: 1, name: 'Mock' });
|
||||
});
|
||||
```
|
||||
|
||||
### Partial Mock
|
||||
|
||||
```ts
|
||||
vi.mock('./utils', async (importOriginal) => {
|
||||
const actual = await importOriginal();
|
||||
return {
|
||||
...actual,
|
||||
specificFunction: vi.fn(),
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
### Auto-mock with Spy
|
||||
|
||||
```ts
|
||||
// Keep implementation but spy on calls
|
||||
vi.mock('./calculator', { spy: true });
|
||||
|
||||
import { add } from './calculator';
|
||||
|
||||
test('spy on module', () => {
|
||||
const result = add(1, 2); // Real implementation
|
||||
expect(result).toBe(3);
|
||||
expect(add).toHaveBeenCalledWith(1, 2);
|
||||
});
|
||||
```
|
||||
|
||||
### Manual Mocks (**mocks**)
|
||||
|
||||
```
|
||||
src/
|
||||
__mocks__/
|
||||
axios.ts # Mocks 'axios'
|
||||
api/
|
||||
__mocks__/
|
||||
client.ts # Mocks './client'
|
||||
client.ts
|
||||
```
|
||||
|
||||
```ts
|
||||
// Just call vi.mock with no factory
|
||||
vi.mock('axios');
|
||||
vi.mock('./api/client');
|
||||
```
|
||||
|
||||
## Dynamic Mocking (vi.doMock)
|
||||
|
||||
Not hoisted - use for dynamic imports:
|
||||
|
||||
```ts
|
||||
test('dynamic mock', async () => {
|
||||
vi.doMock('./config', () => ({
|
||||
apiUrl: 'http://test.local',
|
||||
}));
|
||||
|
||||
const { apiUrl } = await import('./config');
|
||||
expect(apiUrl).toBe('http://test.local');
|
||||
|
||||
vi.doUnmock('./config');
|
||||
});
|
||||
```
|
||||
|
||||
## Mock Timers
|
||||
|
||||
```ts
|
||||
import { afterEach, beforeEach, vi } from 'vitest';
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
test('timers', () => {
|
||||
const fn = vi.fn();
|
||||
setTimeout(fn, 1000);
|
||||
|
||||
expect(fn).not.toHaveBeenCalled();
|
||||
|
||||
vi.advanceTimersByTime(1000);
|
||||
expect(fn).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Other timer methods
|
||||
vi.runAllTimers(); // Run all pending timers
|
||||
vi.runOnlyPendingTimers(); // Run only currently pending
|
||||
vi.advanceTimersToNextTimer(); // Advance to next timer
|
||||
```
|
||||
|
||||
### Async Timer Methods
|
||||
|
||||
```ts
|
||||
test('async timers', async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
let resolved = false;
|
||||
setTimeout(
|
||||
() =>
|
||||
Promise.resolve().then(() => {
|
||||
resolved = true;
|
||||
}),
|
||||
100,
|
||||
);
|
||||
|
||||
await vi.advanceTimersByTimeAsync(100);
|
||||
expect(resolved).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
## Mock Dates
|
||||
|
||||
```ts
|
||||
vi.setSystemTime(new Date('2024-01-01'));
|
||||
expect(new Date().getFullYear()).toBe(2024);
|
||||
|
||||
vi.useRealTimers(); // Restore
|
||||
```
|
||||
|
||||
## Mock Globals
|
||||
|
||||
```ts
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn(() => Promise.resolve({ json: () => ({ data: 'mock' }) })),
|
||||
);
|
||||
|
||||
// Restore
|
||||
vi.unstubAllGlobals();
|
||||
```
|
||||
|
||||
## Mock Environment Variables
|
||||
|
||||
```ts
|
||||
vi.stubEnv('API_KEY', 'test-key');
|
||||
expect(import.meta.env.API_KEY).toBe('test-key');
|
||||
|
||||
// Restore
|
||||
vi.unstubAllEnvs();
|
||||
```
|
||||
|
||||
## Clearing Mocks
|
||||
|
||||
```ts
|
||||
const fn = vi.fn();
|
||||
fn();
|
||||
|
||||
fn.mockClear(); // Clear call history
|
||||
fn.mockReset(); // Clear history + implementation
|
||||
fn.mockRestore(); // Restore original (for spies)
|
||||
|
||||
// Global
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
vi.restoreAllMocks();
|
||||
```
|
||||
|
||||
## Config Auto-Reset
|
||||
|
||||
```ts
|
||||
// vitest.config.ts
|
||||
defineConfig({
|
||||
test: {
|
||||
clearMocks: true, // Clear before each test
|
||||
mockReset: true, // Reset before each test
|
||||
restoreMocks: true, // Restore after each test
|
||||
unstubEnvs: true, // Restore env vars
|
||||
unstubGlobals: true, // Restore globals
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Hoisted Variables for Mocks
|
||||
|
||||
```ts
|
||||
const mockFn = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock('./module', () => ({
|
||||
getData: mockFn,
|
||||
}));
|
||||
|
||||
import { getData } from './module';
|
||||
|
||||
test('hoisted mock', () => {
|
||||
mockFn.mockReturnValue('test');
|
||||
expect(getData()).toBe('test');
|
||||
});
|
||||
```
|
||||
|
||||
## Key Points
|
||||
|
||||
- `vi.mock` is hoisted - called before imports
|
||||
- Use `vi.doMock` for dynamic, non-hoisted mocking
|
||||
- Always restore mocks to avoid test pollution
|
||||
- Use `{ spy: true }` to keep implementation but track calls
|
||||
- `vi.hoisted` lets you reference variables in mock factories
|
||||
|
||||
<!--
|
||||
Source references:
|
||||
- https://vitest.dev/guide/mocking.html
|
||||
- https://vitest.dev/api/vi.html
|
||||
-->
|
||||
Reference in New Issue
Block a user