or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mdbenchmarking.mdbrowser-testing.mdconfiguration.mdindex.mdmocking.mdnode-apis.mdreporters.mdtest-definition.mdtimers.mdtype-testing.md

mocking.mddocs/

0

# Mocking and Spying

1

2

Comprehensive mocking through the `vi` namespace (alias: `vitest`) for module mocking, function spying, and test isolation.

3

4

## Mock Functions

5

6

```typescript { .api }

7

vi.fn<Args extends any[], R>(implementation?: (...args: Args) => R): Mock<Args, R>;

8

vi.isMockFunction(fn: any): fn is Mock;

9

10

interface Mock<Args extends any[], R> {

11

(...args: Args): R;

12

13

mock: {

14

calls: Args[];

15

results: MockResult<R>[];

16

instances: any[];

17

invocationCallOrder: number[];

18

lastCall?: Args;

19

};

20

21

mockImplementation(fn: (...args: Args) => R): this;

22

mockImplementationOnce(fn: (...args: Args) => R): this;

23

mockReturnValue(value: R): this;

24

mockReturnValueOnce(value: R): this;

25

mockResolvedValue(value: Awaited<R>): this;

26

mockResolvedValueOnce(value: Awaited<R>): this;

27

mockRejectedValue(value: any): this;

28

mockRejectedValueOnce(value: any): this;

29

mockClear(): this;

30

mockReset(): this;

31

mockRestore(): this;

32

getMockName(): string;

33

mockName(name: string): this;

34

}

35

```

36

37

**Examples:**

38

```typescript

39

// Basic mock

40

const mockFn = vi.fn((x: number) => x * 2);

41

mockFn(5); // Returns 10

42

expect(mockFn).toHaveBeenCalledWith(5);

43

44

// Return values

45

const fn = vi.fn();

46

fn.mockReturnValue(42);

47

fn(); // 42

48

49

fn.mockReturnValueOnce(100).mockReturnValueOnce(200);

50

fn(); // 100

51

fn(); // 200

52

fn(); // 42 (default)

53

54

// Async values

55

const asyncFn = vi.fn();

56

asyncFn.mockResolvedValue('success');

57

await asyncFn(); // 'success'

58

59

asyncFn.mockRejectedValue(new Error('failed'));

60

await asyncFn(); // throws Error

61

62

// Implementation

63

const addFn = vi.fn();

64

addFn.mockImplementation((a, b) => a + b);

65

addFn(2, 3); // 5

66

67

addFn.mockImplementationOnce((a, b) => a * b);

68

addFn(2, 3); // 6

69

addFn(2, 3); // 5 (back to default)

70

```

71

72

## Spying

73

74

```typescript { .api }

75

vi.spyOn<T, K extends keyof T>(

76

object: T,

77

method: K,

78

accessType?: 'get' | 'set'

79

): MockInstance;

80

```

81

82

**Examples:**

83

```typescript

84

// Spy on method (preserves original behavior)

85

const calculator = {

86

add: (a: number, b: number) => a + b,

87

};

88

89

const spy = vi.spyOn(calculator, 'add');

90

calculator.add(2, 3); // Returns 5 (original behavior)

91

expect(spy).toHaveBeenCalledWith(2, 3);

92

spy.mockRestore(); // Restore original

93

94

// Spy on getter

95

const obj = {

96

get value() { return 42; }

97

};

98

99

const spy = vi.spyOn(obj, 'value', 'get');

100

obj.value; // 42

101

expect(spy).toHaveBeenCalled();

102

103

spy.mockReturnValue(100);

104

obj.value; // 100

105

106

// Spy on setter

107

let _value = 0;

108

const obj = {

109

set value(v: number) { _value = v; }

110

};

111

112

const spy = vi.spyOn(obj, 'value', 'set');

113

obj.value = 42;

114

expect(spy).toHaveBeenCalledWith(42);

115

```

116

117

## Module Mocking

118

119

```typescript { .api }

120

// Hoisted mocking (runs before imports)

121

vi.mock(path: string, factory?: () => any): void;

122

vi.unmock(path: string): void;

123

124

// Non-hoisted mocking (for conditional mocks)

125

vi.doMock(path: string, factory?: () => any): void;

126

vi.doUnmock(path: string): void;

127

128

// Import helpers

129

vi.importActual<T>(path: string): Promise<T>;

130

vi.importMock<T>(path: string): Promise<T>;

131

vi.hoisted<T>(factory: () => T): T;

132

```

133

134

**Examples:**

135

```typescript

136

// Mock entire module

137

vi.mock('./api', () => ({

138

fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: 'John' })),

139

deleteUser: vi.fn(() => Promise.resolve(true)),

140

}));

141

142

test('mocked module', async () => {

143

const { fetchUser } = await import('./api');

144

const user = await fetchUser(1);

145

expect(user).toEqual({ id: 1, name: 'John' });

146

});

147

148

// Partial mock (keep some real exports)

149

vi.mock('./utils', async () => {

150

const actual = await vi.importActual<typeof import('./utils')>('./utils');

151

return {

152

...actual,

153

fetchData: vi.fn(() => 'mocked data'),

154

};

155

});

156

157

// Conditional mocking

158

test('conditional mock', async () => {

159

vi.doMock('./config', () => ({ apiUrl: 'https://test.api' }));

160

const { apiUrl } = await import('./config');

161

expect(apiUrl).toBe('https://test.api');

162

vi.doUnmock('./config');

163

});

164

165

// Hoisted factory (share values across mocks)

166

const { mockUsers } = vi.hoisted(() => ({

167

mockUsers: [{ id: 1, name: 'John' }],

168

}));

169

170

vi.mock('./database', () => ({

171

getUsers: vi.fn(() => Promise.resolve(mockUsers)),

172

}));

173

```

174

175

## Mock Object

176

177

Create deep mock of objects.

178

179

```typescript { .api }

180

vi.mockObject<T>(value: T, options?: { spy?: boolean }): MaybeMockedDeep<T>;

181

```

182

183

**Examples:**

184

```typescript

185

// Mock object (methods return undefined)

186

const api = {

187

fetchUser: (id: number) => Promise.resolve({ id, name: 'John' }),

188

deleteUser: (id: number) => Promise.resolve(true),

189

};

190

191

const mockedApi = vi.mockObject(api);

192

expect(vi.isMockFunction(mockedApi.fetchUser)).toBe(true);

193

await expect(mockedApi.fetchUser(1)).resolves.toBeUndefined();

194

195

// Configure mock behavior

196

mockedApi.fetchUser.mockResolvedValue({ id: 1, name: 'Test' });

197

await expect(mockedApi.fetchUser(1)).resolves.toEqual({ id: 1, name: 'Test' });

198

199

// Spy on object (preserves behavior)

200

const api2 = {

201

add: (a: number, b: number) => a + b,

202

multiply: (a: number, b: number) => a * b,

203

};

204

205

const spiedApi = vi.mockObject(api2, { spy: true });

206

expect(spiedApi.add(2, 3)).toBe(5); // Original behavior

207

expect(spiedApi.add).toHaveBeenCalledWith(2, 3); // But tracked

208

```

209

210

## Typed Mocks

211

212

```typescript { .api }

213

vi.mocked<T>(item: T, deep?: false): Mocked<T>;

214

vi.mocked<T>(item: T, deep: true): MaybeMockedDeep<T>;

215

```

216

217

**Example:**

218

```typescript

219

import { fetchUser } from './api';

220

221

vi.mock('./api');

222

223

test('typed mock', () => {

224

const mockFetchUser = vi.mocked(fetchUser);

225

mockFetchUser.mockResolvedValue({ id: 1, name: 'John' });

226

// TypeScript knows about mockResolvedValue

227

});

228

```

229

230

## Mock State Management

231

232

```typescript { .api }

233

vi.clearAllMocks() // Clear call history only

234

vi.resetAllMocks() // Clear history + reset implementation

235

vi.restoreAllMocks() // Restore original implementations (spies)

236

```

237

238

| Method | Call History | Implementation | Restore Original |

239

|--------|--------------|----------------|------------------|

240

| `mockClear()` | ✓ Clear | Keep | No |

241

| `mockReset()` | ✓ Clear | ✓ Reset | No |

242

| `mockRestore()` | ✓ Clear | ✓ Reset | ✓ Yes (spies only) |

243

244

**Example:**

245

```typescript

246

beforeEach(() => {

247

vi.clearAllMocks(); // Clear all mock call history

248

});

249

250

afterEach(() => {

251

vi.restoreAllMocks(); // Restore all spies

252

});

253

254

test('mock cleanup', () => {

255

const mockFn = vi.fn(() => 42);

256

mockFn();

257

mockFn();

258

expect(mockFn).toHaveBeenCalledTimes(2);

259

260

vi.clearAllMocks(); // Clear history, keep implementation

261

expect(mockFn).toHaveBeenCalledTimes(0);

262

expect(mockFn()).toBe(42); // Still returns 42

263

264

vi.resetAllMocks(); // Clear history, reset implementation

265

expect(mockFn()).toBeUndefined();

266

});

267

```

268

269

## Global Stubs

270

271

```typescript { .api }

272

vi.stubGlobal(name: string, value: any): VitestUtils;

273

vi.unstubAllGlobals(): VitestUtils;

274

vi.stubEnv(name: string, value: string): VitestUtils;

275

vi.unstubAllEnvs(): VitestUtils;

276

```

277

278

**Examples:**

279

```typescript

280

afterEach(() => {

281

vi.unstubAllGlobals();

282

vi.unstubAllEnvs();

283

});

284

285

test('stub fetch', () => {

286

const mockFetch = vi.fn(() => Promise.resolve({ ok: true }));

287

vi.stubGlobal('fetch', mockFetch);

288

289

// fetch is now mocked

290

expect(globalThis.fetch).toBe(mockFetch);

291

});

292

293

test('stub env vars', () => {

294

vi.stubEnv('NODE_ENV', 'test');

295

vi.stubEnv('API_KEY', 'test-key');

296

297

expect(process.env.NODE_ENV).toBe('test');

298

expect(process.env.API_KEY).toBe('test-key');

299

});

300

```

301

302

## Async Wait Utilities

303

304

```typescript { .api }

305

vi.waitFor<T>(

306

callback: () => T | Promise<T>,

307

options?: { timeout?: number; interval?: number }

308

): Promise<T>;

309

310

vi.waitUntil<T>(

311

callback: () => T | Promise<T>,

312

options?: { timeout?: number; interval?: number }

313

): Promise<T>;

314

```

315

316

**Difference:**

317

- `waitFor`: Retries until callback **doesn't throw**

318

- `waitUntil`: Retries until callback returns **truthy value** (fails if throws)

319

320

**Examples:**

321

```typescript

322

test('wait for condition', async () => {

323

let ready = false;

324

setTimeout(() => { ready = true }, 500);

325

326

await vi.waitUntil(

327

() => ready,

328

{ timeout: 1000, interval: 50 }

329

);

330

331

expect(ready).toBe(true);

332

});

333

334

test('wait for no errors', async () => {

335

const server = createServer();

336

337

await vi.waitFor(

338

() => {

339

if (!server.isReady) throw new Error('Not ready');

340

},

341

{ timeout: 2000, interval: 100 }

342

);

343

344

expect(server.isReady).toBe(true);

345

});

346

347

test('wait for element', async () => {

348

const element = await vi.waitUntil(

349

() => document.querySelector('.loaded'),

350

{ timeout: 1000 }

351

);

352

353

expect(element).toBeTruthy();

354

});

355

```

356

357

## Module Cache

358

359

```typescript { .api }

360

vi.resetModules(): VitestUtils;

361

vi.dynamicImportSettled(): Promise<void>;

362

```

363

364

**Examples:**

365

```typescript

366

test('reset modules', async () => {

367

const { value: value1 } = await import('./module');

368

369

vi.resetModules(); // Clear cache

370

371

const { value: value2 } = await import('./module');

372

// Fresh module instance

373

});

374

375

test('wait for dynamic imports', async () => {

376

const promises = [

377

import('./module1'),

378

import('./module2'),

379

import('./module3'),

380

];

381

382

await vi.dynamicImportSettled();

383

// All imports completed

384

});

385

```

386

387

## Common Mock Patterns

388

389

### Mock Fetch

390

391

```typescript

392

test('mock fetch', async () => {

393

const mockFetch = vi.fn(() =>

394

Promise.resolve({

395

ok: true,

396

json: () => Promise.resolve({ data: 'test' }),

397

})

398

);

399

400

vi.stubGlobal('fetch', mockFetch);

401

402

const response = await fetch('/api/data');

403

const data = await response.json();

404

405

expect(data).toEqual({ data: 'test' });

406

expect(mockFetch).toHaveBeenCalledWith('/api/data');

407

408

vi.unstubAllGlobals();

409

});

410

```

411

412

### Mock Date

413

414

```typescript

415

test('mock date', () => {

416

const mockDate = new Date('2024-01-01T00:00:00Z');

417

vi.setSystemTime(mockDate);

418

419

expect(new Date().toISOString()).toBe('2024-01-01T00:00:00.000Z');

420

421

vi.useRealTimers(); // Restore

422

});

423

```

424

425

### Partial Module Mock

426

427

```typescript

428

vi.mock('./api', async () => {

429

const actual = await vi.importActual<typeof import('./api')>('./api');

430

return {

431

...actual,

432

fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: 'Test' })),

433

};

434

});

435

436

test('partial mock', async () => {

437

const { fetchUser, otherFunction } = await import('./api');

438

439

expect(vi.isMockFunction(fetchUser)).toBe(true); // Mocked

440

expect(vi.isMockFunction(otherFunction)).toBe(false); // Real

441

});

442

```

443

444

### Automatic Cleanup

445

446

```typescript

447

beforeEach(() => {

448

// Setup mocks

449

});

450

451

afterEach(() => {

452

vi.clearAllMocks(); // Clear call history

453

vi.resetAllMocks(); // Reset implementations

454

vi.restoreAllMocks(); // Restore spies

455

vi.unstubAllGlobals(); // Restore globals

456

vi.unstubAllEnvs(); // Restore env vars

457

});

458

```

459

460

## Type Definitions

461

462

```typescript { .api }

463

interface MockContext<Args extends any[], R> {

464

calls: Args[];

465

results: MockResult<R>[];

466

instances: any[];

467

invocationCallOrder: number[];

468

lastCall?: Args;

469

}

470

471

interface MockResult<T> {

472

type: 'return' | 'throw';

473

value: T;

474

}

475

476

type Mocked<T> = T extends (...args: any[]) => any

477

? MockedFunction<T>

478

: T extends object

479

? MockedObject<T>

480

: T;

481

482

type MockedFunction<T extends (...args: any[]) => any> = Mock<

483

Parameters<T>,

484

ReturnType<T>

485

> & T;

486

487

type MockedObject<T> = {

488

[K in keyof T]: T[K] extends (...args: any[]) => any

489

? MockedFunction<T[K]>

490

: T[K];

491

} & T;

492

```

493

494

## Mock Strategy Guide

495

496

| Scenario | Use | Reason |

497

|----------|-----|--------|

498

| Track function calls | `vi.fn()` | Full control over implementation |

499

| Keep original behavior | `vi.spyOn()` | Monitor calls without changing behavior |

500

| Replace entire module | `vi.mock()` | Isolate from dependencies |

501

| Partial module mock | `vi.mock() + importActual()` | Mock some exports, keep others |

502

| Mock global APIs | `vi.stubGlobal()` | Replace built-in globals (fetch, etc) |

503

| Conditional mock | `vi.doMock()` | Mock only in specific tests |

504

| Share mock state | `vi.hoisted()` | Access variables in factory |

505