[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

fix!: rewrite spying implementation to make module mocking more intuitive (#8363)

authored by

Vladimir and committed by
GitHub
(Aug 1, 2025, 4:13 PM +0200) 9e412de3 459efba6

+3527 -1210
+2 -5
pnpm-lock.yaml
··· 749 749 specifier: ^1.4.0 750 750 version: 1.4.0 751 751 752 - packages/spy: 753 - dependencies: 754 - tinyspy: 755 - specifier: ^4.0.3 756 - version: 4.0.3 752 + packages/spy: {} 757 753 758 754 packages/ui: 759 755 dependencies: ··· 8179 8175 source-map@0.8.0-beta.0: 8180 8176 resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} 8181 8177 engines: {node: '>= 8'} 8178 + deprecated: The work that was done in this beta branch won't be included in future versions 8182 8179 8183 8180 sourcemap-codec@1.4.8: 8184 8181 resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
+36 -1
docs/.vitepress/config.ts
··· 502 502 { 503 503 text: 'Mocking', 504 504 link: '/guide/mocking', 505 + collapsed: true, 506 + items: [ 507 + { 508 + text: 'Mocking Dates', 509 + link: '/guide/mocking#dates', 510 + }, 511 + { 512 + text: 'Mocking Functions', 513 + link: '/guide/mocking#functions', 514 + }, 515 + { 516 + text: 'Mocking Globals', 517 + link: '/guide/mocking#globals', 518 + }, 519 + { 520 + text: 'Mocking Modules', 521 + link: '/guide/mocking-modules', 522 + }, 523 + { 524 + text: 'Mocking File System', 525 + link: '/guide/mocking#file-system', 526 + }, 527 + { 528 + text: 'Mocking Requests', 529 + link: '/guide/mocking#requests', 530 + }, 531 + { 532 + text: 'Mocking Timers', 533 + link: '/guide/mocking#timers', 534 + }, 535 + { 536 + text: 'Mocking Classes', 537 + link: '/guide/mocking#classes', 538 + }, 539 + ], 505 540 }, 506 541 { 507 542 text: 'Parallelism', ··· 586 621 link: '/api/', 587 622 }, 588 623 { 589 - text: 'Mock Functions', 624 + text: 'Mocks', 590 625 link: '/api/mock', 591 626 }, 592 627 {
+48 -36
docs/api/mock.md
··· 1 - # Mock Functions 1 + # Mocks 2 2 3 - You can create a mock function to track its execution with `vi.fn` method. If you want to track a method on an already created object, you can use `vi.spyOn` method: 3 + 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: 4 4 5 5 ```js 6 6 import { vi } from 'vitest' ··· 18 18 getApplesSpy.mock.calls.length === 1 19 19 ``` 20 20 21 - You should use mock assertions (e.g., [`toHaveBeenCalled`](/api/expect#tohavebeencalled)) on [`expect`](/api/expect) to assert mock result. This API reference describes available properties and methods to manipulate mock behavior. 21 + You should use mock assertions (e.g., [`toHaveBeenCalled`](/api/expect#tohavebeencalled)) on [`expect`](/api/expect) to assert mock results. This API reference describes available properties and methods to manipulate mock behavior. 22 22 23 23 ::: tip 24 24 The custom function implementation in the types below is marked with a generic `<T>`. ··· 30 30 function getMockImplementation(): T | undefined 31 31 ``` 32 32 33 - Returns current mock implementation if there is one. 33 + Returns the current mock implementation if there is one. 34 34 35 35 If the mock was created with [`vi.fn`](/api/vi#vi-fn), it will use the provided method as the mock implementation. 36 36 ··· 42 42 function getMockName(): string 43 43 ``` 44 44 45 - Use it to return the name assigned to the mock with the `.mockName(name)` method. By default, it will return `vi.fn()`. 45 + Use it to return the name assigned to the mock with the `.mockName(name)` method. By default, `vi.fn()` mocks will return `'vi.fn()'`, while spies created with `vi.spyOn` will keep the original name. 46 46 47 47 ## mockClear 48 48 49 49 ```ts 50 - function mockClear(): MockInstance<T> 50 + function mockClear(): Mock<T> 51 51 ``` 52 52 53 53 Clears all information about every call. After calling it, all properties on `.mock` will return to their initial state. This method does not reset implementations. It is useful for cleaning up mocks between different assertions. ··· 72 72 ## mockName 73 73 74 74 ```ts 75 - function mockName(name: string): MockInstance<T> 75 + function mockName(name: string): Mock<T> 76 76 ``` 77 77 78 78 Sets the internal mock name. This is useful for identifying the mock when an assertion fails. ··· 80 80 ## mockImplementation 81 81 82 82 ```ts 83 - function mockImplementation(fn: T): MockInstance<T> 83 + function mockImplementation(fn: T): Mock<T> 84 84 ``` 85 85 86 86 Accepts a function to be used as the mock implementation. TypeScript expects the arguments and return type to match those of the original function. ··· 102 102 ## mockImplementationOnce 103 103 104 104 ```ts 105 - function mockImplementationOnce(fn: T): MockInstance<T> 105 + function mockImplementationOnce(fn: T): Mock<T> 106 106 ``` 107 107 108 108 Accepts a function to be used as the mock implementation. TypeScript expects the arguments and return type to match those of the original function. This method can be chained to produce different results for multiple function calls. ··· 135 135 function withImplementation( 136 136 fn: T, 137 137 cb: () => void 138 - ): MockInstance<T> 138 + ): Mock<T> 139 139 function withImplementation( 140 140 fn: T, 141 141 cb: () => Promise<void> 142 - ): Promise<MockInstance<T>> 142 + ): Promise<Mock<T>> 143 143 ``` 144 144 145 145 Overrides the original mock implementation temporarily while the callback is being executed. ··· 177 177 ## mockRejectedValue 178 178 179 179 ```ts 180 - function mockRejectedValue(value: unknown): MockInstance<T> 180 + function mockRejectedValue(value: unknown): Mock<T> 181 181 ``` 182 182 183 - Accepts an error that will be rejected when async function is called. 183 + Accepts an error that will be rejected when an async function is called. 184 184 185 185 ```ts 186 186 const asyncMock = vi.fn().mockRejectedValue(new Error('Async error')) ··· 191 191 ## mockRejectedValueOnce 192 192 193 193 ```ts 194 - function mockRejectedValueOnce(value: unknown): MockInstance<T> 194 + function mockRejectedValueOnce(value: unknown): Mock<T> 195 195 ``` 196 196 197 197 Accepts a value that will be rejected during the next function call. If chained, each consecutive call will reject the specified value. ··· 209 209 ## mockReset 210 210 211 211 ```ts 212 - function mockReset(): MockInstance<T> 212 + function mockReset(): Mock<T> 213 213 ``` 214 214 215 - Does what [`mockClear`](#mockClear) does and resets inner implementation to the original function. 216 - This also resets all "once" implementations. 215 + Does what [`mockClear`](#mockClear) does and resets the mock implementation. This also resets all "once" implementations. 217 216 218 - Note that resetting a mock from `vi.fn()` will set implementation to an empty function that returns `undefined`. 219 - resetting a mock from `vi.fn(impl)` will restore implementation to `impl`. 217 + Note that resetting a mock from `vi.fn()` will set the implementation to an empty function that returns `undefined`. 218 + Resetting a mock from `vi.fn(impl)` will reset the implementation to `impl`. 220 219 221 220 This is useful when you want to reset a mock to its original state. 222 221 ··· 241 240 ## mockRestore 242 241 243 242 ```ts 244 - function mockRestore(): MockInstance<T> 243 + function mockRestore(): Mock<T> 245 244 ``` 246 245 247 - Does what [`mockReset`](#mockReset) does and restores original descriptors of spied-on objects. 246 + Does what [`mockReset`](#mockreset) does and restores the original descriptors of spied-on objects, if the mock was created with [`vi.spyOn`](/api/vi#vi-spyon). 248 247 249 - Note that restoring a mock from `vi.fn()` will set implementation to an empty function that returns `undefined`. 250 - Restoring a mock from `vi.fn(impl)` will restore implementation to `impl`. 248 + `mockRestore` on a `vi.fn()` mock is identical to [`mockReset`](#mockreset). 251 249 252 250 ```ts 253 251 const person = { ··· 270 268 ## mockResolvedValue 271 269 272 270 ```ts 273 - function mockResolvedValue(value: Awaited<ReturnType<T>>): MockInstance<T> 271 + function mockResolvedValue(value: Awaited<ReturnType<T>>): Mock<T> 274 272 ``` 275 273 276 274 Accepts a value that will be resolved when the async function is called. TypeScript will only accept values that match the return type of the original function. ··· 284 282 ## mockResolvedValueOnce 285 283 286 284 ```ts 287 - function mockResolvedValueOnce(value: Awaited<ReturnType<T>>): MockInstance<T> 285 + function mockResolvedValueOnce(value: Awaited<ReturnType<T>>): Mock<T> 288 286 ``` 289 287 290 288 Accepts a value that will be resolved during the next function call. TypeScript will only accept values that match the return type of the original function. If chained, each consecutive call will resolve the specified value. ··· 305 303 ## mockReturnThis 306 304 307 305 ```ts 308 - function mockReturnThis(): MockInstance<T> 306 + function mockReturnThis(): Mock<T> 309 307 ``` 310 308 311 309 Use this if you need to return the `this` context from the method without invoking the actual implementation. This is a shorthand for: ··· 319 317 ## mockReturnValue 320 318 321 319 ```ts 322 - function mockReturnValue(value: ReturnType<T>): MockInstance<T> 320 + function mockReturnValue(value: ReturnType<T>): Mock<T> 323 321 ``` 324 322 325 323 Accepts a value that will be returned whenever the mock function is called. TypeScript will only accept values that match the return type of the original function. ··· 335 333 ## mockReturnValueOnce 336 334 337 335 ```ts 338 - function mockReturnValueOnce(value: ReturnType<T>): MockInstance<T> 336 + function mockReturnValueOnce(value: ReturnType<T>): Mock<T> 339 337 ``` 340 338 341 339 Accepts a value that will be returned whenever the mock function is called. TypeScript will only accept values that match the return type of the original function. ··· 379 377 const lastCall: Parameters<T> | undefined 380 378 ``` 381 379 382 - This contains the arguments of the last call. If mock wasn't called, it will return `undefined`. 380 + This contains the arguments of the last call. If the mock wasn't called, it will return `undefined`. 383 381 384 382 ## mock.results 385 383 ··· 388 386 type: 'return' 389 387 /** 390 388 * The value that was returned from the function. 391 - * If function returned a Promise, then this will be a resolved value. 389 + * If the function returned a Promise, then this will be a resolved value. 392 390 */ 393 391 value: T 394 392 } ··· 418 416 419 417 - `'return'` - function returned without throwing. 420 418 - `'throw'` - function threw a value. 419 + - `'incomplete'` - the function did not finish running yet. 421 420 422 421 The `value` property contains the returned value or thrown error. If the function returned a `Promise`, then `result` will always be `'return'` even if the promise was rejected. 423 422 ··· 450 449 ## mock.settledResults 451 450 452 451 ```ts 452 + interface MockSettledResultIncomplete { 453 + type: 'incomplete' 454 + value: undefined 455 + } 456 + 453 457 interface MockSettledResultFulfilled<T> { 454 458 type: 'fulfilled' 455 459 value: T ··· 463 467 export type MockSettledResult<T> 464 468 = | MockSettledResultFulfilled<T> 465 469 | MockSettledResultRejected 470 + | MockSettledResultIncomplete 466 471 467 472 const settledResults: MockSettledResult<Awaited<ReturnType<T>>>[] 468 473 ``` 469 474 470 - An array containing all values that were `resolved` or `rejected` from the function. 475 + An array containing all values that were resolved or rejected by the function. 471 476 472 - This array will be empty if the function was never resolved or rejected. 477 + If the function returned non-promise values, the `value` will be kept as is, but the `type` will still says `fulfilled` or `rejected`. 478 + 479 + Until the value is resolved or rejected, the `settledResult` type will be `incomplete`. 473 480 474 481 ```js 475 482 const fn = vi.fn().mockResolvedValueOnce('result') 476 483 477 484 const result = fn() 478 485 479 - fn.mock.settledResults === [] 486 + fn.mock.settledResults === [ 487 + { 488 + type: 'incomplete', 489 + value: undefined, 490 + }, 491 + ] 480 492 481 493 await result 482 494 ··· 533 545 const instances: ReturnType<T>[] 534 546 ``` 535 547 536 - This property is an array containing all instances that were created when the mock was called with the `new` keyword. Note that this is an actual context (`this`) of the function, not a return value. 548 + This property is an array containing all instances that were created when the mock was called with the `new` keyword. Note that this is the actual context (`this`) of the function, not a return value. 537 549 538 550 ::: warning 539 - If mock was instantiated with `new MyClass()`, then `mock.instances` will be an array with one value: 551 + If the mock was instantiated with `new MyClass()`, then `mock.instances` will be an array with one value: 540 552 541 553 ```js 542 554 const MyClass = vi.fn() ··· 545 557 MyClass.mock.instances[0] === a 546 558 ``` 547 559 548 - If you return a value from constructor, it will not be in `instances` array, but instead inside `results`: 560 + If you return a value from the constructor, it will not be in the `instances` array, but instead inside `results`: 549 561 550 562 ```js 551 563 const Spy = vi.fn(() => ({ method: vi.fn() }))
+202 -46
docs/api/vi.md
··· 16 16 17 17 ### vi.mock 18 18 19 - - **Type**: `(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void` 20 - - **Type**: `<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void` 19 + ```ts 20 + interface MockOptions { 21 + spy?: boolean 22 + } 23 + 24 + interface MockFactory<T> { 25 + (importOriginal: () => T): unknown 26 + } 27 + 28 + function mock( 29 + path: string, 30 + factory?: MockOptions | MockFactory<unknown> 31 + ): void 32 + function mock<T>( 33 + module: Promise<T>, 34 + factory?: MockOptions | MockFactory<T> 35 + ): void 36 + ``` 21 37 22 38 Substitutes all imported modules from provided `path` with another module. You can use configured Vite aliases inside a path. The call to `vi.mock` is hoisted, so it doesn't matter where you call it. It will always be executed before all imports. If you need to reference some variables outside of its scope, you can define them inside [`vi.hoisted`](#vi-hoisted) and reference them inside `vi.mock`. 23 39 ··· 159 175 160 176 ### vi.doMock 161 177 162 - - **Type**: `(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void` 163 - - **Type**: `<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void` 178 + ```ts 179 + function doMock( 180 + path: string, 181 + factory?: MockOptions | MockFactory<unknown> 182 + ): void 183 + function doMock<T>( 184 + module: Promise<T>, 185 + factory?: MockOptions | MockFactory<T> 186 + ): void 187 + ``` 164 188 165 189 The same as [`vi.mock`](#vi-mock), but it's not hoisted to the top of the file, so you can reference variables in the global file scope. The next [dynamic import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) of the module will be mocked. 166 190 ··· 207 231 208 232 ### vi.mocked 209 233 210 - - **Type**: `<T>(obj: T, deep?: boolean) => MaybeMockedDeep<T>` 211 - - **Type**: `<T>(obj: T, options?: { partial?: boolean; deep?: boolean }) => MaybePartiallyMockedDeep<T>` 234 + ```ts 235 + function mocked<T>( 236 + object: T, 237 + deep?: boolean 238 + ): MaybeMockedDeep<T> 239 + function mocked<T>( 240 + object: T, 241 + options?: { partial?: boolean; deep?: boolean } 242 + ): MaybePartiallyMockedDeep<T> 243 + ``` 212 244 213 245 Type helper for TypeScript. Just returns the object that was passed. 214 246 ··· 243 275 244 276 ### vi.importActual 245 277 246 - - **Type**: `<T>(path: string) => Promise<T>` 278 + ```ts 279 + function importActual<T>(path: string): Promise<T> 280 + ``` 247 281 248 282 Imports module, bypassing all checks if it should be mocked. Can be useful if you want to mock module partially. 249 283 ··· 257 291 258 292 ### vi.importMock 259 293 260 - - **Type**: `<T>(path: string) => Promise<MaybeMockedDeep<T>>` 294 + ```ts 295 + function importMock<T>(path: string): Promise<MaybeMockedDeep<T>> 296 + ``` 261 297 262 298 Imports a module with all of its properties (including nested properties) mocked. Follows the same rules that [`vi.mock`](#vi-mock) does. For the rules applied, see [algorithm](/guide/mocking#automocking-algorithm). 263 299 264 300 ### vi.unmock 265 301 266 - - **Type**: `(path: string | Promise<Module>) => void` 302 + ```ts 303 + function unmock(path: string | Promise<Module>): void 304 + ``` 267 305 268 306 Removes module from the mocked registry. All calls to import will return the original module even if it was mocked before. This call is hoisted to the top of the file, so it will only unmock modules that were defined in `setupFiles`, for example. 269 307 270 308 ### vi.doUnmock 271 309 272 - - **Type**: `(path: string | Promise<Module>) => void` 310 + ```ts 311 + function doUnmock(path: string | Promise<Module>): void 312 + ``` 273 313 274 314 The same as [`vi.unmock`](#vi-unmock), but is not hoisted to the top of the file. The next import of the module will import the original module instead of the mock. This will not unmock previously imported modules. 275 315 ··· 308 348 309 349 ### vi.resetModules 310 350 311 - - **Type**: `() => Vitest` 351 + ```ts 352 + function resetModules(): Vitest 353 + ``` 312 354 313 355 Resets modules registry by clearing the cache of all modules. This allows modules to be reevaluated when reimported. Top-level imports cannot be re-evaluated. Might be useful to isolate modules where local state conflicts between tests. 314 356 ··· 338 380 ::: 339 381 340 382 ### vi.dynamicImportSettled 383 + 384 + ```ts 385 + function dynamicImportSettled(): Promise<void> 386 + ``` 341 387 342 388 Wait for all imports to load. Useful, if you have a synchronous call that starts importing a module that you cannot wait otherwise. 343 389 ··· 370 416 371 417 ### vi.fn 372 418 373 - - **Type:** `(fn?: Function) => Mock` 419 + ```ts 420 + function fn(fn?: Procedure | Constructable): Mock 421 + ``` 374 422 375 423 Creates a spy on a function, though can be initiated without one. Every time a function is invoked, it stores its call arguments, returns, and instances. Also, you can manipulate its behavior with [methods](/api/mock). 376 424 If no function is given, mock will return `undefined`, when invoked. ··· 390 438 expect(getApples).toHaveNthReturnedWith(2, 5) 391 439 ``` 392 440 441 + You can also pass down a class to `vi.fn`: 442 + 443 + ```ts 444 + const Cart = vi.fn(class { 445 + get = () => 0 446 + }) 447 + 448 + const cart = new Cart() 449 + expect(Cart).toHaveBeenCalled() 450 + ``` 451 + 393 452 ### vi.mockObject <Version>3.2.0</Version> 394 453 395 - - **Type:** `<T>(value: T) => MaybeMockedDeep<T>` 454 + ```ts 455 + function mockObject<T>(value: T): MaybeMockedDeep<T> 456 + ``` 396 457 397 458 Deeply mocks properties and methods of a given object in the same way as `vi.mock()` mocks module exports. See [automocking](/guide/mocking.html#automocking-algorithm) for the detail. 398 459 ··· 428 489 429 490 ### vi.isMockFunction 430 491 431 - - **Type:** `(fn: Function) => boolean` 492 + ```ts 493 + function isMockFunction(fn: unknown): asserts fn is Mock 494 + ``` 432 495 433 496 Checks that a given parameter is a mock function. If you are using TypeScript, it will also narrow down its type. 434 497 435 498 ### vi.clearAllMocks 499 + 500 + ```ts 501 + function clearAllMocks(): Vitest 502 + ``` 436 503 437 504 Calls [`.mockClear()`](/api/mock#mockclear) on all spies. 438 505 This will clear mock history without affecting mock implementations. 439 506 440 507 ### vi.resetAllMocks 441 508 509 + ```ts 510 + function resetAllMocks(): Vitest 511 + ``` 512 + 442 513 Calls [`.mockReset()`](/api/mock#mockreset) on all spies. 443 - This will clear mock history and reset each mock's implementation to its original. 514 + This will clear mock history and reset each mock's implementation. 444 515 445 516 ### vi.restoreAllMocks 446 517 447 - Calls [`.mockRestore()`](/api/mock#mockrestore) on all spies. 448 - This will clear mock history, restore all original mock implementations, and restore original descriptors of spied-on objects. 518 + ```ts 519 + function restoreAllMocks(): Vitest 520 + ``` 521 + 522 + This restores all original implementations on spies created with [`vi.spyOn`](#vi-spyon). 523 + 524 + After the mock was restored, you can spy on it again. 525 + 526 + ::: warning 527 + This method also does not affect mocks created during [automocking](/guide/mocking-modules#mocking-a-module). 528 + 529 + Note that unlike [`mock.mockRestore`](/api/mock#mockrestore), `vi.restoreAllMocks` will not clear mock history or reset the mock implementation 530 + ::: 449 531 450 532 ### vi.spyOn 451 533 452 - - **Type:** `<T, K extends keyof T>(object: T, method: K, accessType?: 'get' | 'set') => MockInstance` 534 + ```ts 535 + function spyOn<T, K extends keyof T>( 536 + object: T, 537 + key: K, 538 + accessor?: 'get' | 'set' 539 + ): Mock<T[K]> 540 + ``` 453 541 454 542 Creates a spy on a method or getter/setter of an object similar to [`vi.fn()`](#vi-fn). It returns a [mock function](/api/mock). 455 543 ··· 509 597 ::: 510 598 511 599 ::: tip 512 - You can call [`vi.restoreAllMocks`](#vi-restoreallmocks) inside [`afterEach`](/api/#aftereach) (or enable [`test.restoreMocks`](/config/#restoreMocks)) to restore all methods to their original implementations. This will restore the original [object descriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), so you won't be able to change method's implementation: 600 + You can call [`vi.restoreAllMocks`](#vi-restoreallmocks) inside [`afterEach`](/api/#aftereach) (or enable [`test.restoreMocks`](/config/#restoreMocks)) to restore all methods to their original implementations after every test. This will restore the original [object descriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), so you won't be able to change method's implementation anymore, unless you spy again: 513 601 514 602 ```ts 515 603 const cart = { ··· 545 633 546 634 ### vi.stubEnv {#vi-stubenv} 547 635 548 - - **Type:** `<T extends string>(name: T, value: T extends "PROD" | "DEV" | "SSR" ? boolean : string | undefined) => Vitest` 636 + ```ts 637 + function stubEnv<T extends string>( 638 + name: T, 639 + value: T extends 'PROD' | 'DEV' | 'SSR' ? boolean : string | undefined 640 + ): Vitest 641 + ``` 549 642 550 643 Changes the value of environmental variable on `process.env` and `import.meta.env`. You can restore its value by calling `vi.unstubAllEnvs`. 551 644 ··· 579 672 580 673 ### vi.unstubAllEnvs {#vi-unstuballenvs} 581 674 582 - - **Type:** `() => Vitest` 675 + ```ts 676 + function unstubAllEnvs(): Vitest 677 + ``` 583 678 584 679 Restores all `import.meta.env` and `process.env` values that were changed with `vi.stubEnv`. When it's called for the first time, Vitest remembers the original value and will store it, until `unstubAllEnvs` is called again. 585 680 ··· 608 703 609 704 ### vi.stubGlobal 610 705 611 - - **Type:** `(name: string | number | symbol, value: unknown) => Vitest` 706 + ```ts 707 + function stubGlobal( 708 + name: string | number | symbol, 709 + value: unknown 710 + ): Vitest 711 + ``` 612 712 613 713 Changes the value of global variable. You can restore its original value by calling `vi.unstubAllGlobals`. 614 714 ··· 637 737 638 738 ### vi.unstubAllGlobals {#vi-unstuballglobals} 639 739 640 - - **Type:** `() => Vitest` 740 + ```ts 741 + function unstubAllGlobals(): Vitest 742 + ``` 641 743 642 744 Restores all global values on `globalThis`/`global` (and `window`/`top`/`self`/`parent`, if you are using `jsdom` or `happy-dom` environment) that were changed with `vi.stubGlobal`. When it's called for the first time, Vitest remembers the original value and will store it, until `unstubAllGlobals` is called again. 643 745 ··· 670 772 671 773 ### vi.advanceTimersByTime 672 774 673 - - **Type:** `(ms: number) => Vitest` 775 + ```ts 776 + function advanceTimersByTime(ms: number): Vitest 777 + ``` 674 778 675 779 This method will invoke every initiated timer until the specified number of milliseconds is passed or the queue is empty - whatever comes first. 676 780 ··· 687 791 688 792 ### vi.advanceTimersByTimeAsync 689 793 690 - - **Type:** `(ms: number) => Promise<Vitest>` 794 + ```ts 795 + function advanceTimersByTimeAsync(ms: number): Promise<Vitest> 796 + ``` 691 797 692 798 This method will invoke every initiated timer until the specified number of milliseconds is passed or the queue is empty - whatever comes first. This will include asynchronously set timers. 693 799 ··· 704 810 705 811 ### vi.advanceTimersToNextTimer 706 812 707 - - **Type:** `() => Vitest` 813 + ```ts 814 + function advanceTimersToNextTimer(): Vitest 815 + ``` 708 816 709 817 Will call next available timer. Useful to make assertions between each timer call. You can chain call it to manage timers by yourself. 710 818 ··· 719 827 720 828 ### vi.advanceTimersToNextTimerAsync 721 829 722 - - **Type:** `() => Promise<Vitest>` 830 + ```ts 831 + function advanceTimersToNextTimerAsync(): Promise<Vitest> 832 + ``` 723 833 724 834 Will call next available timer and wait until it's resolved if it was set asynchronously. Useful to make assertions between each timer call. 725 835 ··· 734 844 await vi.advanceTimersToNextTimerAsync() // log: 3 735 845 ``` 736 846 737 - ### vi.advanceTimersToNextFrame <Version>2.1.0</Version> {#vi-advancetimerstonextframe} 847 + ### vi.advanceTimersToNextFrame {#vi-advancetimerstonextframe} 738 848 739 - - **Type:** `() => Vitest` 849 + ```ts 850 + function advanceTimersToNextFrame(): Vitest 851 + ``` 740 852 741 853 Similar to [`vi.advanceTimersByTime`](https://vitest.dev/api/vi#vi-advancetimersbytime), but will advance timers by the milliseconds needed to execute callbacks currently scheduled with `requestAnimationFrame`. 742 854 ··· 754 866 755 867 ### vi.getTimerCount 756 868 757 - - **Type:** `() => number` 869 + ```ts 870 + function getTimerCount(): number 871 + ``` 758 872 759 873 Get the number of waiting timers. 760 874 761 875 ### vi.clearAllTimers 762 876 877 + ```ts 878 + function clearAllTimers(): void 879 + ``` 880 + 763 881 Removes all timers that are scheduled to run. These timers will never run in the future. 764 882 765 883 ### vi.getMockedSystemTime 766 884 767 - - **Type**: `() => Date | null` 885 + ```ts 886 + function getMockedSystemTime(): Date | null 887 + ``` 768 888 769 889 Returns mocked current date. If date is not mocked the method will return `null`. 770 890 771 891 ### vi.getRealSystemTime 772 892 773 - - **Type**: `() => number` 893 + ```ts 894 + function getRealSystemTime(): number 895 + ``` 774 896 775 897 When using `vi.useFakeTimers`, `Date.now` calls are mocked. If you need to get real time in milliseconds, you can call this function. 776 898 777 899 ### vi.runAllTicks 778 900 779 - - **Type:** `() => Vitest` 901 + ```ts 902 + function runAllTicks(): Vitest 903 + ``` 780 904 781 905 Calls every microtask that was queued by `process.nextTick`. This will also run all microtasks scheduled by themselves. 782 906 783 907 ### vi.runAllTimers 784 908 785 - - **Type:** `() => Vitest` 909 + ```ts 910 + function runAllTimers(): Vitest 911 + ``` 786 912 787 913 This method will invoke every initiated timer until the timer queue is empty. It means that every timer called during `runAllTimers` will be fired. If you have an infinite interval, it will throw after 10 000 tries (can be configured with [`fakeTimers.loopLimit`](/config/#faketimers-looplimit)). 788 914 ··· 805 931 806 932 ### vi.runAllTimersAsync 807 933 808 - - **Type:** `() => Promise<Vitest>` 934 + ```ts 935 + function runAllTimersAsync(): Promise<Vitest> 936 + ``` 809 937 810 938 This method will asynchronously invoke every initiated timer until the timer queue is empty. It means that every timer called during `runAllTimersAsync` will be fired even asynchronous timers. If you have an infinite interval, 811 939 it will throw after 10 000 tries (can be configured with [`fakeTimers.loopLimit`](/config/#faketimers-looplimit)). ··· 822 950 823 951 ### vi.runOnlyPendingTimers 824 952 825 - - **Type:** `() => Vitest` 953 + ```ts 954 + function runOnlyPendingTimers(): Vitest 955 + ``` 826 956 827 957 This method will call every timer that was initiated after [`vi.useFakeTimers`](#vi-usefaketimers) call. It will not fire any timer that was initiated during its call. 828 958 ··· 837 967 838 968 ### vi.runOnlyPendingTimersAsync 839 969 840 - - **Type:** `() => Promise<Vitest>` 970 + ```ts 971 + function runOnlyPendingTimersAsync(): Promise<Vitest> 972 + ``` 841 973 842 974 This method will asynchronously call every timer that was initiated after [`vi.useFakeTimers`](#vi-usefaketimers) call, even asynchronous ones. It will not fire any timer that was initiated during its call. 843 975 ··· 864 996 865 997 ### vi.setSystemTime 866 998 867 - - **Type**: `(date: string | number | Date) => void` 999 + ```ts 1000 + function setSystemTime(date: string | number | Date): Vitest 1001 + ``` 868 1002 869 1003 If fake timers are enabled, this method simulates a user changing the system clock (will affect date related API like `hrtime`, `performance.now` or `new Date()`) - however, it will not fire any timers. If fake timers are not enabled, this method will only mock `Date.*` calls. 870 1004 ··· 885 1019 886 1020 ### vi.useFakeTimers 887 1021 888 - - **Type:** `(config?: FakeTimerInstallOpts) => Vitest` 1022 + ```ts 1023 + function useFakeTimers(config?: FakeTimerInstallOpts): Vitest 1024 + ``` 889 1025 890 1026 To enable mocking timers, you need to call this method. It will wrap all further calls to timers (such as `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`, `setImmediate`, `clearImmediate`, and `Date`) until [`vi.useRealTimers()`](#vi-userealtimers) is called. 891 1027 ··· 900 1036 901 1037 ### vi.isFakeTimers {#vi-isfaketimers} 902 1038 903 - - **Type:** `() => boolean` 1039 + ```ts 1040 + function isFakeTimers(): boolean 1041 + ``` 904 1042 905 1043 Returns `true` if fake timers are enabled. 906 1044 907 1045 ### vi.useRealTimers 908 1046 909 - - **Type:** `() => Vitest` 1047 + ```ts 1048 + function useRealTimers(): Vitest 1049 + ``` 910 1050 911 1051 When timers have run out, you may call this method to return mocked timers to its original implementations. All timers that were scheduled before will be discarded. 912 1052 ··· 916 1056 917 1057 ### vi.waitFor {#vi-waitfor} 918 1058 919 - - **Type:** `<T>(callback: WaitForCallback<T>, options?: number | WaitForOptions) => Promise<T>` 1059 + ```ts 1060 + function waitFor<T>( 1061 + callback: WaitForCallback<T>, 1062 + options?: number | WaitForOptions 1063 + ): Promise<T> 1064 + ``` 920 1065 921 1066 Wait for the callback to execute successfully. If the callback throws an error or returns a rejected promise it will continue to wait until it succeeds or times out. 922 1067 ··· 978 1123 979 1124 ### vi.waitUntil {#vi-waituntil} 980 1125 981 - - **Type:** `<T>(callback: WaitUntilCallback<T>, options?: number | WaitUntilOptions) => Promise<T>` 1126 + ```ts 1127 + function waitUntil<T>( 1128 + callback: WaitUntilCallback<T>, 1129 + options?: number | WaitUntilOptions 1130 + ): Promise<T> 1131 + ``` 982 1132 983 1133 This is similar to `vi.waitFor`, but if the callback throws any errors, execution is immediately interrupted and an error message is received. If the callback returns falsy value, the next check will continue until truthy value is returned. This is useful when you need to wait for something to exist before taking the next step. 984 1134 ··· 1003 1153 1004 1154 ### vi.hoisted {#vi-hoisted} 1005 1155 1006 - - **Type**: `<T>(factory: () => T) => T` 1156 + ```ts 1157 + function hoisted<T>(factory: () => T): T 1158 + ``` 1007 1159 1008 1160 All static `import` statements in ES modules are hoisted to the top of the file, so any code that is defined before the imports will actually be executed after imports are evaluated. 1009 1161 ··· 1080 1232 1081 1233 ### vi.setConfig 1082 1234 1083 - - **Type**: `RuntimeConfig` 1235 + ```ts 1236 + function setConfig(config: RuntimeOptions): void 1237 + ``` 1084 1238 1085 1239 Updates config for the current test file. This method supports only config options that will affect the current test file: 1086 1240 ··· 1105 1259 1106 1260 ### vi.resetConfig 1107 1261 1108 - - **Type**: `RuntimeConfig` 1262 + ```ts 1263 + function resetConfig(): void 1264 + ``` 1109 1265 1110 1266 If [`vi.setConfig`](#vi-setconfig) was called before, this will reset config to the original state.
+6 -5
docs/config/index.md
··· 1643 1643 - **Type:** `boolean` 1644 1644 - **Default:** `false` 1645 1645 1646 - Will call [`.mockClear()`](/api/mock#mockclear) on all spies before each test. 1646 + Will call [`vi.clearAllMocks()`](/api/vi#vi-clearallmocks) before each test. 1647 1647 This will clear mock history without affecting mock implementations. 1648 1648 1649 1649 ### mockReset ··· 1651 1651 - **Type:** `boolean` 1652 1652 - **Default:** `false` 1653 1653 1654 - Will call [`.mockReset()`](/api/mock#mockreset) on all spies before each test. 1655 - This will clear mock history and reset each implementation to its original. 1654 + Will call [`vi.resetAllMocks()`](/api/vi#vi-resetallmocks) before each test. 1655 + This will clear mock history and reset each implementation. 1656 1656 1657 1657 ### restoreMocks 1658 1658 1659 1659 - **Type:** `boolean` 1660 1660 - **Default:** `false` 1661 1661 1662 - Will call [`.mockRestore()`](/api/mock#mockrestore) on all spies before each test. 1663 - This will clear mock history, restore each implementation to its original, and restore original descriptors of spied-on objects.. 1662 + Will call [`vi.restoreAllMocks()`](/api/vi#vi-restoreallmocks) before each test. 1663 + 1664 + This restores all original implementations on spies created with [`vi.spyOn`](#vi-spyon). 1664 1665 1665 1666 ### unstubEnvs {#unstubenvs} 1666 1667
+46 -1
docs/guide/migration.md
··· 116 116 117 117 Note that now if you provide an arrow function, you will get [`<anonymous> is not a constructor` error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Not_a_constructor) when the mock is called. 118 118 119 + ### Changes to Mocking 120 + 121 + Alongside new features like supporting constructors, Vitest 4 creates mocks differently to address several module mocking issues that we received over the years. This release attemts to make module spies less confusing, especially when working with classes. 122 + 123 + - `vi.fn().getMockName()` now returns `vi.fn()` by default instead of `spy`. This can affect snapshots with mocks - the name will be changed from `[MockFunction spy]` to `[MockFunction]`. Spies created with `vi.spyOn` will keep using the original name by default for better debugging experience 124 + - `vi.restoreAllMocks` no longer resets the state of spies and only restores spies created manually with `vi.spyOn`, automocks are no longer affected by this function (this also affects the config option [`restoreMocks`](/config/#restoremocks)). Note that `.mockRestore` will still reset the mock implementation and clear the state 125 + - Calling `vi.spyOn` on a mock now returns the same mock 126 + - Automocked instance methods are now properly isolated, but share a state with the prototype. Overriding the prototype implementation will always affect instance methods unless the methods have a custom mock implementation of their own. Calling `.mockReset` on the mock also no longer breaks that inheritance. 127 + ```ts 128 + import { AutoMockedClass } from './example.js' 129 + const instance1 = new AutoMockedClass() 130 + const instance2 = new AutoMockedClass() 131 + 132 + instance1.method.mockReturnValue(42) 133 + 134 + expect(instance1.method()).toBe(42) 135 + expect(instance2.method()).toBe(undefined) 136 + 137 + expect(AutoMockedClass.prototype.method).toHaveBeenCalledTimes(2) 138 + 139 + instance1.method.mockReset() 140 + AutoMockedClass.prototype.method.mockReturnValue(100) 141 + 142 + expect(instance1.method()).toBe(100) 143 + expect(instance2.method()).toBe(100) 144 + 145 + expect(AutoMockedClass.prototype.method).toHaveBeenCalledTimes(4) 146 + ``` 147 + - Automocked methods can no longer be restored, even with a manual `.mockRestore`. Automocked modules with `spy: true` will keep working as before 148 + - Automocked getters no longer call the original getter. By default, automocked getters now return `undefined`. You can keep using `vi.spyOn(object, name, 'get')` to spy on a getter and change its implementation 149 + - The mock `vi.fn(implementation).mockReset()` now correctly returns the mock implementation in `.getMockImplementation()` 150 + - `vi.fn().mock.invocationCallOrder` now starts with `1`, like Jest does, instead of `0` 151 + 119 152 ### Standalone mode with filename filter 120 153 121 154 To improve user experience, Vitest will now start running the matched files when [`--standalone`](/guide/cli#standalone) is used with filename filter. ··· 181 214 182 215 If you decide to keep globals disabled, be aware that common libraries like [`testing-library`](https://testing-library.com/) will not run auto DOM [cleanup](https://testing-library.com/docs/svelte-testing-library/api/#cleanup). 183 216 184 - ### `spy.mockReset` 217 + ### `mock.mockReset` 185 218 186 219 Jest's [`mockReset`](https://jestjs.io/docs/mock-function-api#mockfnmockreset) replaces the mock implementation with an 187 220 empty function that returns `undefined`. 188 221 189 222 Vitest's [`mockReset`](/api/mock#mockreset) resets the mock implementation to its original. 190 223 That is, resetting a mock created by `vi.fn(impl)` will reset the mock implementation to `impl`. 224 + 225 + ### `mock.mock` is Persistent 226 + 227 + Jest will recreate the mock state when `.mockClear` is called, meaning you always need to access it as a getter. Vitest, on the other hand, holds a persistent reference to the state, meaning you can reuse it: 228 + 229 + ```ts 230 + const mock = vi.fn() 231 + const state = mock.mock 232 + mock.mockClear() 233 + 234 + expect(state).toBe(mock.mock) // fails in Jest 235 + ``` 191 236 192 237 ### Module Mocks 193 238
+410
docs/guide/mocking-modules.md
··· 1 + # Mocking Modules 2 + 3 + ## Defining a Module 4 + 5 + Before mocking a "module", we should define what it is. In Vitest context, the "module" is a file that exports something. Using [plugins](https://vite.dev/guide/api-plugin.html), any file can be turned into a JavaScript module. The "module object" is a namespace object that holds dynamic references to exported identifiers. Simply put, it's an object with exported methods and properties. In this example, `example.js` is a module that exports `method` and `variable`: 6 + 7 + ```js [example.js] 8 + export function answer() { 9 + // ... 10 + return 42 11 + } 12 + 13 + export const variable = 'example' 14 + ``` 15 + 16 + The `exampleObject` here is a module object: 17 + 18 + ```js [example.test.js] 19 + import * as exampleObject from './example.js' 20 + ``` 21 + 22 + The `exampleObject` will always exist even if you imported the example using named imports: 23 + 24 + ```js [example.test.js] 25 + import { answer, variable } from './example.js' 26 + ``` 27 + 28 + You can only reference `exampleObject` outside the example module itself. For example, in a test. 29 + 30 + ## Mocking a Module 31 + 32 + For the purpose of this guide, let's introduce some definitions. 33 + 34 + - **Mocked module** is a module that was completely replaced with another one. 35 + - **Spied module** is a mocked module, but its exported methods keep the original implementation. They can also be tracked. 36 + - **Mocked export** is a module export, which invocations can be tracked. 37 + - **Spied export** is a mocked export. 38 + 39 + To mock a module completely, you can use the [`vi.mock` API](/api/vi#vi-mock). You can define a new module dynamically by providing a factory that returns a new module as a second argument: 40 + 41 + ```ts 42 + import { vi } from 'vitest' 43 + 44 + // The ./example.js module will be replaced with 45 + // the result of a factory function, and the 46 + // original ./example.js module will never be called 47 + vi.mock(import('./example.js'), () => { 48 + return { 49 + answer() { 50 + // ... 51 + return 42 52 + }, 53 + variable: 'mock', 54 + } 55 + }) 56 + ``` 57 + 58 + ::: tip 59 + Remember that you can call `vi.mock` in a [setup file](/config/#setupfiles) to apply the module mock in every test file automatically. 60 + ::: 61 + 62 + ::: tip 63 + Note the usage of dynamic import: `import('./example.ts')`. Vitest will strip it before the code is executed, but it allows TypeScript to properly validate the string and type the `importOriginal` method in your IDE or CLI. 64 + ::: 65 + 66 + If your code is trying to access a method that was not returned from this factory, Vitest will throw an error with a helpful message. Note that `answer` is not mocked, i.e. it cannot be tracked. To make it trackable, use `vi.fn()` instead: 67 + 68 + ```ts 69 + import { vi } from 'vitest' 70 + 71 + vi.mock(import('./example.js'), () => { 72 + return { 73 + answer: vi.fn(), 74 + variable: 'mock', 75 + } 76 + }) 77 + ``` 78 + 79 + The factory method accepts an `importOriginal` function that will execute the original module and return its module object: 80 + 81 + ```ts 82 + import { expect, vi } from 'vitest' 83 + import { answer } from './example.js' 84 + 85 + vi.mock(import('./example.js'), async (importOriginal) => { 86 + const originalModule = await importOriginal() 87 + return { 88 + answer: vi.fn(originalModule.answer), 89 + variable: 'mock', 90 + } 91 + }) 92 + 93 + expect(answer()).toBe(42) 94 + 95 + expect(answer).toHaveBeenCalled() 96 + expect(answer).toHaveReturned(42) 97 + ``` 98 + 99 + ::: warning 100 + Note that `importOriginal` is asynchronous and needs to be awaited. 101 + ::: 102 + 103 + In the above example, we provided the original `answer` to the `vi.fn()` call so it can keep calling it while being tracked at the same time. 104 + 105 + If you require the use of `importOriginal`, consider spying on the export directly via another API: `vi.spyOn`. Instead of replacing the whole module, you can spy only on a single exported method. To do that, you need to import the module as a namespace object: 106 + 107 + ```ts 108 + import { expect, vi } from 'vitest' 109 + import * as exampleObject from './example.js' 110 + 111 + const spy = vi.spyOn(exampleObject, 'answer').mockReturnValue(0) 112 + 113 + expect(exampleObject.answer()).toBe(0) 114 + expect(exampleObject.answer).toHaveBeenCalled() 115 + ``` 116 + 117 + ::: danger Browser Mode Support 118 + This will not work in the [Browser Mode](/guide/browser/) because it uses the browser's native ESM support to serve modules. The module namespace object is sealed and can't be reconfigured. To bypass this limitation, Vitest supports `{ spy: true }` option in `vi.mock('./example.js')`. This will automatically spy on every export in the module without replacing them with fake ones. 119 + 120 + ```ts 121 + import { vi } from 'vitest' 122 + import * as exampleObject from './example.js' 123 + 124 + vi.mock('./example.js', { spy: true }) 125 + 126 + vi.mocked(exampleObject.answer).mockReturnValue(0) 127 + ``` 128 + ::: 129 + 130 + ::: warning 131 + 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 as the function that called it is called after `vi.spyOn`: 132 + 133 + ```ts [source.js] 134 + import { answer } from './example.js' 135 + 136 + export function question() { 137 + if (answer() === 42) { 138 + return 'Ultimate Question of Life, the Universe, and Everything' 139 + } 140 + 141 + return 'Unknown Question' 142 + } 143 + ``` 144 + ::: 145 + 146 + Note that `vi.spyOn` will only spy on calls that were done after it spied on the method. So, if the function is executed at the top level during an import or it was called before the spying, `vi.spyOn` will not be able to report on it. 147 + 148 + To automatically mock any module before it is imported, you can call `vi.mock` with a path: 149 + 150 + ```ts 151 + import { vi } from 'vitest' 152 + 153 + vi.mock(import('./example.js')) 154 + ``` 155 + 156 + If the file `./__mocks__/example.js` exists, then Vitest will load it instead. Otherwise, Vitest will load the original module and replace everything recursively: 157 + 158 + - All arrays will be empty 159 + - All primitives will stay untouched 160 + - All getters will return `undefined` 161 + - All methods will return `undefined` 162 + - All objects will be deeply cloned 163 + - All instances of classes and their prototypes will be cloned 164 + 165 + To disable this behavior, you can pass down `spy: true` as the second argument: 166 + 167 + ```ts 168 + import { vi } from 'vitest' 169 + 170 + vi.mock(import('./example.js'), { spy: true }) 171 + ``` 172 + 173 + Instead of returning `undefined`, all methods will call the original implementation, but you can still keep track of these calls: 174 + 175 + ```ts 176 + import { expect, vi } from 'vitest' 177 + import { answer } from './example.js' 178 + 179 + vi.mock(import('./example.js'), { spy: true }) 180 + 181 + // calls the original implementation 182 + expect(answer()).toBe(42) 183 + // vitest can still track the invocations 184 + expect(answer).toHaveBeenCalled() 185 + ``` 186 + 187 + One nice thing that mocked modules support is sharing the state between the instance and its prototype. Consider this module: 188 + 189 + ```ts [answer.js] 190 + export class Answer { 191 + constructor(value) { 192 + this._value = value 193 + } 194 + 195 + value() { 196 + return this._value 197 + } 198 + } 199 + ``` 200 + 201 + By mocking it, we can keep track of every invocation of `.value()` even without having access to the instance itself: 202 + 203 + ```ts [answer.test.js] 204 + import { expect, test, vi } from 'vitest' 205 + import { Answer } from './answer.js' 206 + 207 + vi.mock(import('./answer.js'), { spy: true }) 208 + 209 + test('instance inherits the state', () => { 210 + // these invocations could be private inside another function 211 + // that you don't have access to in your test 212 + const answer1 = new Answer(42) 213 + const answer2 = new Answer(0) 214 + 215 + expect(answer1.value()).toBe(42) 216 + expect(answer1.value).toHaveBeenCalled() 217 + // note that different instances have their own states 218 + expect(answer2.value).not.toHaveBeenCalled() 219 + 220 + expect(answer2.value()).toBe(0) 221 + 222 + // but the prototype state accumulates all calls 223 + expect(Answer.prototype.value).toHaveBeenCalledTimes(2) 224 + expect(Answer.prototype.value).toHaveReturned(42) 225 + expect(Answer.prototype.value).toHaveReturned(0) 226 + }) 227 + ``` 228 + 229 + This can be very useful to track calls to instances that are never exposed. 230 + 231 + ## Mocking Non-existing Module 232 + 233 + Vitest supports mocking virtual modules. These modules don't exist on the file system, but your code imports them. For example, this can happen when your development environment is different from production. One common example is mocking `vscode` APIs in your unit tests. 234 + 235 + By default, Vitest will fail transforming files if it cannot find the source of the import. To bypass this, you need to specify it in your config. You can either always redirect the import to a file, or just signal Vite to ignore it and use the `vi.mock` factory to define its exports. 236 + 237 + To redirect the import, use [`test.alias`](/config/#alias) config option: 238 + 239 + ```ts [vitest.config.ts] 240 + import { defineConfig } from 'vitest/config' 241 + import { resolve } from 'node:path' 242 + 243 + export default defineConfig({ 244 + test: { 245 + alias: { 246 + vscode: resolve(import.meta.dirname, './mock/vscode.js'), 247 + }, 248 + }, 249 + }) 250 + ``` 251 + 252 + To mark the module as always resolved, return the same string from `resolveId` hook of a plugin: 253 + 254 + ```ts [vitest.config.ts] 255 + import { defineConfig } from 'vitest/config' 256 + import { resolve } from 'node:path' 257 + 258 + export default defineConfig({ 259 + plugins: [ 260 + { 261 + name: 'virtual-vscode', 262 + resolveId(id) { 263 + if (id === 'vscode') { 264 + return 'vscode' 265 + } 266 + } 267 + } 268 + ] 269 + }) 270 + ``` 271 + 272 + Now you can use `vi.mock` as usual in your tests: 273 + 274 + ```ts 275 + import { vi } from 'vitest' 276 + 277 + vi.mock(import('vscode'), () => { 278 + return { 279 + window: { 280 + createOutputChannel: vi.fn(), 281 + } 282 + } 283 + }) 284 + ``` 285 + 286 + ## How it Works 287 + 288 + Vitest implements different module mocking mechanisms depending on the environment. The only feature they share is the plugin transformer. When Vitest sees that a file has `vi.mock` inside, it will transform every static import into a dynamic one and move the `vi.mock` call to the top of the file. This allows Vitest to register the mock before the import happens without breaking the ESM rule of hoisted imports. 289 + 290 + ::: code-group 291 + ```ts [example.js] 292 + import { answer } from './answer.js' 293 + 294 + vi.mock(import('./answer.js')) 295 + 296 + console.log(answer) 297 + ``` 298 + ```ts [example.transformed.js] 299 + vi.mock('./answer.js') 300 + 301 + const __vitest_module_0__ = await __handle_mock__( 302 + () => import('./answer.js') 303 + ) 304 + // to keep the live binding, we have to access 305 + // the export on the module namespace 306 + console.log(__vitest_module_0__.answer()) 307 + ``` 308 + ::: 309 + 310 + The `__handle_mock__` wrapper just makes sure the mock is resolved before the import is initiated, it doesn't modify the module in any way. 311 + 312 + The module mocking plugins are available in the [`@vitest/mocker` package](https://github.com/vitest-dev/vitest/tree/main/packages/mocker). 313 + 314 + ### JSDOM, happy-dom, Node 315 + 316 + When you run your tests in an emulated environment, Vitest creates a [module runner](https://vite.dev/guide/api-environment-runtimes.html#modulerunner) that can consume Vite code. The module runner is designed in such a way that Vitest can hook into the module evaluation and replace it with the mock, if it was registered. This means that Vitest runs your code in an ESM-like environment, but it doesn't use native ESM mechanism directly. This allows the test runner to bend the rules around ES Modules immutability, allowing users to call `vi.spyOn` on a seemingly ES Module. 317 + 318 + ### Browser Mode 319 + 320 + Vitest uses native ESM in the Browser Mode. This means that we cannot replace the module so easily. Instead, Vitest intercepts the fetch request (via playwright's `page.route` or a Vite plugin API if using `preview` or `webdriverio`) and serves transformed code, if the module was mocked. 321 + 322 + For example, if the module is automocked, Vitest can parse static exports and create a placeholder module: 323 + 324 + ::: code-group 325 + ```ts [answer.js] 326 + export function answer() { 327 + return 42 328 + } 329 + ``` 330 + ```ts [answer.transformed.js] 331 + function answer() { 332 + return 42 333 + } 334 + 335 + const __private_module__ = { 336 + [Symbol.toStringTag]: 'Module', 337 + answer: vi.fn(answer), 338 + } 339 + 340 + export const answer = __private_module__.answer 341 + ``` 342 + ::: 343 + 344 + The example is simplified for brevity, but the concept is unchanged. We can inject a `__private_module__` variable into the module to hold the mocked values. If the user called `vi.mock` with `spy: true`, we pass down the original value; otherwise, we create a simple `vi.fn()` mock. 345 + 346 + If user defined a custom factory, this makes it harder to inject the code, but not impossible. When the mocked file is served, we first resolve the factory in the browser, then pass down the keys back to the server, and use them to create a placeholder module: 347 + 348 + ```ts 349 + const resolvedFactoryKeys = await resolveBrowserFactory(url) 350 + const mockedModule = ` 351 + const __private_module__ = getFactoryReturnValue(${url}) 352 + ${resolvedFactoryKeys.map(key => `export const ${key} = __private_module__["${key}"]`).join('\n')} 353 + ` 354 + ``` 355 + 356 + This module can now be served back to the browser. You can inspect the code in the devtools when you run the tests. 357 + 358 + ## Mocking Modules Pitfalls 359 + 360 + Beware that it is not possible to mock calls to methods that are called inside other methods of the same file. For example, in this code: 361 + 362 + ```ts [foobar.js] 363 + export function foo() { 364 + return 'foo' 365 + } 366 + 367 + export function foobar() { 368 + return `${foo()}bar` 369 + } 370 + ``` 371 + 372 + It is not possible to mock the `foo` method from the outside because it is referenced directly. So this code will have no effect on the `foo` call inside `foobar` (but it will affect the `foo` call in other modules): 373 + 374 + ```ts [foobar.test.ts] 375 + import { vi } from 'vitest' 376 + import * as mod from './foobar.js' 377 + 378 + // this will only affect "foo" outside of the original module 379 + vi.spyOn(mod, 'foo') 380 + vi.mock(import('./foobar.js'), async (importOriginal) => { 381 + return { 382 + ...await importOriginal(), 383 + // this will only affect "foo" outside of the original module 384 + foo: () => 'mocked' 385 + } 386 + }) 387 + ``` 388 + 389 + You can confirm this behavior by providing the implementation to the `foobar` method directly: 390 + 391 + ```ts [foobar.test.js] 392 + import * as mod from './foobar.js' 393 + 394 + vi.spyOn(mod, 'foo') 395 + 396 + // exported foo references mocked method 397 + mod.foobar(mod.foo) 398 + ``` 399 + 400 + ```ts [foobar.js] 401 + export function foo() { 402 + return 'foo' 403 + } 404 + 405 + export function foobar(injectedFoo) { 406 + return injectedFoo === foo // false 407 + } 408 + ``` 409 + 410 + This is the intended behavior, and we do not plan to implement a workaround. Consider refactoring your code into multiple files or use techniques such as [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection). We believe that making the application testable is not the responsibility of the test runner, but of the application architecture.
+29 -220
docs/guide/mocking.md
··· 155 155 156 156 ## Modules 157 157 158 - Mock modules observe third-party-libraries, that are invoked in some other code, allowing you to test arguments, output or even redeclare its implementation. 159 - 160 - See the [`vi.mock()` API section](/api/vi#vi-mock) for a more in-depth detailed API description. 161 - 162 - ### Automocking Algorithm 163 - 164 - If your code is importing a mocked module, without any associated `__mocks__` file or `factory` for this module, Vitest will mock the module itself by invoking it and mocking every export. 165 - 166 - The following principles apply 167 - * All arrays will be emptied 168 - * All primitives and collections will stay the same 169 - * All objects will be deeply cloned 170 - * All instances of classes and their prototypes will be deeply cloned 171 - 172 - ### Virtual Modules 173 - 174 - Vitest supports mocking Vite [virtual modules](https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention). It works differently from how virtual modules are treated in Jest. Instead of passing down `virtual: true` to a `vi.mock` function, you need to tell Vite that module exists otherwise it will fail during parsing. You can do that in several ways: 175 - 176 - 1. Provide an alias 177 - 178 - ```ts [vitest.config.js] 179 - import { defineConfig } from 'vitest/config' 180 - import { resolve } from 'node:path' 181 - export default defineConfig({ 182 - test: { 183 - alias: { 184 - '$app/forms': resolve('./mocks/forms.js'), 185 - }, 186 - }, 187 - }) 188 - ``` 189 - 190 - 2. Provide a plugin that resolves a virtual module 191 - 192 - ```ts [vitest.config.js] 193 - import { defineConfig } from 'vitest/config' 194 - export default defineConfig({ 195 - plugins: [ 196 - { 197 - name: 'virtual-modules', 198 - resolveId(id) { 199 - if (id === '$app/forms') { 200 - return 'virtual:$app/forms' 201 - } 202 - }, 203 - }, 204 - ], 205 - }) 206 - ``` 207 - 208 - The benefit of the second approach is that you can dynamically create different virtual entrypoints. If you redirect several virtual modules into a single file, then all of them will be affected by `vi.mock`, so make sure to use unique identifiers. 209 - 210 - ### Mocking Pitfalls 211 - 212 - Beware that it is not possible to mock calls to methods that are called inside other methods of the same file. For example, in this code: 213 - 214 - ```ts [foobar.js] 215 - export function foo() { 216 - return 'foo' 217 - } 218 - 219 - export function foobar() { 220 - return `${foo()}bar` 221 - } 222 - ``` 223 - 224 - It is not possible to mock the `foo` method from the outside because it is referenced directly. So this code will have no effect on the `foo` call inside `foobar` (but it will affect the `foo` call in other modules): 225 - 226 - ```ts [foobar.test.ts] 227 - import { vi } from 'vitest' 228 - import * as mod from './foobar.js' 229 - 230 - // this will only affect "foo" outside of the original module 231 - vi.spyOn(mod, 'foo') 232 - vi.mock('./foobar.js', async (importOriginal) => { 233 - return { 234 - ...await importOriginal<typeof import('./foobar.js')>(), 235 - // this will only affect "foo" outside of the original module 236 - foo: () => 'mocked' 237 - } 238 - }) 239 - ``` 240 - 241 - You can confirm this behaviour by providing the implementation to the `foobar` method directly: 242 - 243 - ```ts [foobar.test.js] 244 - import * as mod from './foobar.js' 245 - 246 - vi.spyOn(mod, 'foo') 247 - 248 - // exported foo references mocked method 249 - mod.foobar(mod.foo) 250 - ``` 251 - 252 - ```ts [foobar.js] 253 - export function foo() { 254 - return 'foo' 255 - } 256 - 257 - export function foobar(injectedFoo) { 258 - return injectedFoo === foo // false 259 - } 260 - ``` 261 - 262 - This is the intended behaviour. It is usually a sign of bad code when mocking is involved in such a manner. Consider refactoring your code into multiple files or improving your application architecture by using techniques such as [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection). 263 - 264 - ### Example 265 - 266 - ```js 267 - import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' 268 - import { Client } from 'pg' 269 - import { failure, success } from './handlers.js' 270 - 271 - // get todos 272 - export async function getTodos(event, context) { 273 - const client = new Client({ 274 - // ...clientOptions 275 - }) 276 - 277 - await client.connect() 278 - 279 - try { 280 - const result = await client.query('SELECT * FROM todos;') 281 - 282 - client.end() 283 - 284 - return success({ 285 - message: `${result.rowCount} item(s) returned`, 286 - data: result.rows, 287 - status: true, 288 - }) 289 - } 290 - catch (e) { 291 - console.error(e.stack) 292 - 293 - client.end() 294 - 295 - return failure({ message: e, status: false }) 296 - } 297 - } 298 - 299 - vi.mock('pg', () => { 300 - const Client = vi.fn() 301 - Client.prototype.connect = vi.fn() 302 - Client.prototype.query = vi.fn() 303 - Client.prototype.end = vi.fn() 304 - 305 - return { Client } 306 - }) 307 - 308 - vi.mock('./handlers.js', () => { 309 - return { 310 - success: vi.fn(), 311 - failure: vi.fn(), 312 - } 313 - }) 314 - 315 - describe('get a list of todo items', () => { 316 - let client 317 - 318 - beforeEach(() => { 319 - client = new Client() 320 - }) 321 - 322 - afterEach(() => { 323 - vi.clearAllMocks() 324 - }) 325 - 326 - it('should return items successfully', async () => { 327 - client.query.mockResolvedValueOnce({ rows: [], rowCount: 0 }) 328 - 329 - await getTodos() 330 - 331 - expect(client.connect).toBeCalledTimes(1) 332 - expect(client.query).toBeCalledWith('SELECT * FROM todos;') 333 - expect(client.end).toBeCalledTimes(1) 334 - 335 - expect(success).toBeCalledWith({ 336 - message: '0 item(s) returned', 337 - data: [], 338 - status: true, 339 - }) 340 - }) 341 - 342 - it('should throw an error', async () => { 343 - const mError = new Error('Unable to retrieve rows') 344 - client.query.mockRejectedValueOnce(mError) 345 - 346 - await getTodos() 347 - 348 - expect(client.connect).toBeCalledTimes(1) 349 - expect(client.query).toBeCalledWith('SELECT * FROM todos;') 350 - expect(client.end).toBeCalledTimes(1) 351 - expect(failure).toBeCalledWith({ message: mError, status: false }) 352 - }) 353 - }) 354 - ``` 158 + See ["Mocking Modules" guide](/guide/mocking-modules). 355 159 356 160 ## File System 357 161 ··· 594 398 595 399 ## Classes 596 400 597 - You can mock an entire class with a single `vi.fn` call - since all classes are also functions, this works out of the box. Beware that currently Vitest doesn't respect the `new` keyword so the `new.target` is always `undefined` in the body of a function. 401 + You can mock an entire class with a single `vi.fn` call. 598 402 599 403 ```ts 600 404 class Dog { ··· 621 425 } 622 426 ``` 623 427 624 - We can re-create this class with ES5 functions: 428 + We can re-create this class with `vi.fn` (or `vi.spyOn().mockImplementation()`): 625 429 626 430 ```ts 627 - const Dog = vi.fn(function (name) { 628 - this.name = name 629 - // mock instance methods in the constructor, each instance will have its own spy 630 - this.greet = vi.fn(() => `Hi! My name is ${this.name}!`) 431 + const Dog = vi.fn(class { 432 + static getType = vi.fn(() => 'mocked animal') 433 + 434 + constructor(name) { 435 + this.name = name 436 + } 437 + 438 + greet = vi.fn(() => `Hi! My name is ${this.name}!`) 439 + speak = vi.fn(() => 'loud bark!') 440 + feed = vi.fn() 631 441 }) 632 - 633 - // notice that static methods are mocked directly on the function, 634 - // not on the instance of the class 635 - Dog.getType = vi.fn(() => 'mocked animal') 636 - 637 - // mock the "speak" and "feed" methods on every instance of a class 638 - // all `new Dog()` instances will inherit and share these spies 639 - Dog.prototype.speak = vi.fn(() => 'loud bark!') 640 - Dog.prototype.feed = vi.fn() 641 442 ``` 642 443 643 444 ::: warning ··· 658 459 Marti instanceof CorrectDogClass // ✅ true 659 460 Newt instanceof IncorrectDogClass // ❌ false! 660 461 ``` 462 + 463 + If you are mocking classes, prefer the class syntax over the function. 661 464 ::: 662 465 663 466 ::: tip WHEN TO USE? ··· 667 470 import { Dog } from './dog.js' 668 471 669 472 vi.mock(import('./dog.js'), () => { 670 - const Dog = vi.fn() 671 - Dog.prototype.feed = vi.fn() 672 - // ... other mocks 473 + const Dog = vi.fn(class { 474 + feed = vi.fn() 475 + // ... other mocks 476 + }) 673 477 return { Dog } 674 478 }) 675 479 ``` ··· 685 489 import { expect, test, vi } from 'vitest' 686 490 import { feed } from '../src/feed.js' 687 491 688 - const Dog = vi.fn() 689 - Dog.prototype.feed = vi.fn() 492 + const Dog = vi.fn(class { 493 + feed = vi.fn() 494 + }) 690 495 691 496 test('can feed dogs', () => { 692 497 const dogMax = new Dog('Max') ··· 712 517 713 518 const Max = new Dog('Max') 714 519 715 - // methods assigned to the prototype are shared between instances 716 - expect(Max.speak).toHaveBeenCalled() 520 + // methods are not shared between instances if you assigned them directly 521 + expect(Max.speak).not.toHaveBeenCalled() 717 522 expect(Max.greet).not.toHaveBeenCalled() 718 523 ``` 719 524 ··· 724 529 725 530 // "vi.mocked" is a type helper, since 726 531 // TypeScript doesn't know that Dog is a mocked class, 727 - // it wraps any function in a MockInstance<T> type 532 + // it wraps any function in a Mock<T> type 728 533 // without validating if the function is a mock 729 534 vi.mocked(dog.speak).mockReturnValue('woof woof') 730 535 ··· 744 549 745 550 ::: tip 746 551 You can also spy on getters and setters using the same method. 552 + ::: 553 + 554 + ::: danger 555 + Using classes with `vi.fn()` was introduced in Vitest 4. Previously, you had to use `function` and `prototype` inheritence directly. See [v3 guide](https://v3.vitest.dev/guide/mocking.html#classes). 747 556 ::: 748 557 749 558 ## Cheat Sheet
-3
packages/spy/package.json
··· 31 31 "scripts": { 32 32 "build": "rimraf dist && rollup -c", 33 33 "dev": "rollup -c --watch" 34 - }, 35 - "dependencies": { 36 - "tinyspy": "^4.0.3" 37 34 } 38 35 }
+3
test/core/vite.config.ts
··· 153 153 if (log.includes('run [...filters]')) { 154 154 return false 155 155 } 156 + if (log.startsWith(`[vitest]`) && log.includes(`did not use 'function' or 'class' in its implementation`)) { 157 + return false 158 + } 156 159 }, 157 160 projects: [ 158 161 project('threads', 'red'),
+103 -66
packages/mocker/src/automocker.ts
··· 1 - import type { MockedModuleType } from './registry' 2 - 3 1 type Key = string | symbol 4 2 3 + export type CreateMockInstanceProcedure = (options?: { 4 + prototypeMembers?: (string | symbol)[] 5 + name?: string | symbol 6 + originalImplementation?: (...args: any[]) => any 7 + keepMembersImplementation?: boolean 8 + }) => any 9 + 5 10 export interface MockObjectOptions { 6 - type: MockedModuleType 11 + type: 'automock' | 'autospy' 7 12 globalConstructors: GlobalConstructors 8 - spyOn: (obj: any, prop: Key) => any 13 + createMockInstance: CreateMockInstanceProcedure 9 14 } 10 15 11 16 export function mockObject( ··· 26 31 } 27 32 } 28 33 34 + const createMock = (currentValue: (...args: any[]) => any) => { 35 + if (!options.createMockInstance) { 36 + throw new Error( 37 + '[@vitest/mocker] `createMockInstance` is not defined. This is a Vitest error. Please open a new issue with reproduction.', 38 + ) 39 + } 40 + const createMockInstance = options.createMockInstance 41 + const prototypeMembers = currentValue.prototype 42 + ? collectFunctionProperties(currentValue.prototype) 43 + : [] 44 + return createMockInstance({ 45 + name: currentValue.name, 46 + prototypeMembers, 47 + originalImplementation: options.type === 'autospy' ? currentValue : undefined, 48 + keepMembersImplementation: options.type === 'autospy', 49 + }) 50 + } 51 + 29 52 const mockPropertiesOf = ( 30 53 container: Record<Key, any>, 31 54 newContainer: Record<Key, any>, ··· 40 63 // Modules define their exports as getters. We want to process those. 41 64 if (!isModule && descriptor.get) { 42 65 try { 43 - Object.defineProperty(newContainer, property, descriptor) 66 + if (options.type === 'autospy') { 67 + Object.defineProperty(newContainer, property, descriptor) 68 + } 69 + else { 70 + Object.defineProperty(newContainer, property, { 71 + configurable: descriptor.configurable, 72 + enumerable: descriptor.enumerable, 73 + // automatically mock getters and setters 74 + // https://github.com/vitest-dev/vitest/issues/8345 75 + get: () => {}, 76 + set: descriptor.set ? () => {} : undefined, 77 + }) 78 + } 44 79 } 45 80 catch { 46 81 // Ignore errors, just move on to the next prop. ··· 49 84 } 50 85 51 86 // Skip special read-only props, we don't want to mess with those. 52 - if (isSpecialProp(property, containerType)) { 87 + if (isReadonlyProp(container[property], property)) { 53 88 continue 54 89 } 55 90 ··· 68 103 const type = getType(value) 69 104 70 105 if (Array.isArray(value)) { 71 - define(newContainer, property, []) 106 + if (options.type === 'automock') { 107 + define(newContainer, property, []) 108 + } 109 + else { 110 + const array = value.map((value) => { 111 + if (value && typeof value === 'object') { 112 + const newObject = {} 113 + mockPropertiesOf(value, newObject) 114 + return newObject 115 + } 116 + if (typeof value === 'function') { 117 + return createMock(value) 118 + } 119 + return value 120 + }) 121 + define(newContainer, property, array) 122 + } 72 123 continue 73 124 } 74 125 ··· 85 136 86 137 // Sometimes this assignment fails for some unknown reason. If it does, 87 138 // just move along. 88 - if (!define(newContainer, property, isFunction ? value : {})) { 139 + if (!define(newContainer, property, isFunction || options.type === 'autospy' ? value : {})) { 89 140 continue 90 141 } 91 142 92 143 if (isFunction) { 93 - if (!options.spyOn) { 94 - throw new Error( 95 - '[@vitest/mocker] `spyOn` is not defined. This is a Vitest error. Please open a new issue with reproduction.', 96 - ) 97 - } 98 - const spyOn = options.spyOn 99 - function mockFunction(this: any) { 100 - // detect constructor call and mock each instance's methods 101 - // so that mock states between prototype/instances don't affect each other 102 - // (jest reference https://github.com/jestjs/jest/blob/2c3d2409879952157433de215ae0eee5188a4384/packages/jest-mock/src/index.ts#L678-L691) 103 - if (this instanceof newContainer[property]) { 104 - for (const { key, descriptor } of getAllMockableProperties( 105 - this, 106 - false, 107 - options.globalConstructors, 108 - )) { 109 - // skip getter since it's not mocked on prototype as well 110 - if (descriptor.get) { 111 - continue 112 - } 113 - 114 - const value = this[key] 115 - const type = getType(value) 116 - const isFunction 117 - = type.includes('Function') && typeof value === 'function' 118 - if (isFunction) { 119 - // mock and delegate calls to original prototype method, which should be also mocked already 120 - const original = this[key] 121 - const mock = spyOn(this, key as string) 122 - .mockImplementation(original) 123 - const origMockReset = mock.mockReset 124 - mock.mockRestore = mock.mockReset = () => { 125 - origMockReset.call(mock) 126 - mock.mockImplementation(original) 127 - return mock 128 - } 129 - } 130 - } 131 - } 132 - } 133 - const mock = spyOn(newContainer, property) 134 - if (options.type === 'automock') { 135 - mock.mockImplementation(mockFunction) 136 - const origMockReset = mock.mockReset 137 - mock.mockRestore = mock.mockReset = () => { 138 - origMockReset.call(mock) 139 - mock.mockImplementation(mockFunction) 140 - return mock 141 - } 142 - } 143 - // tinyspy retains length, but jest doesn't. 144 - Object.defineProperty(newContainer[property], 'length', { value: 0 }) 144 + const mock = createMock(newContainer[property]) 145 + newContainer[property] = mock 145 146 } 146 147 147 148 refs.track(value, newContainer[property]) ··· 184 185 return Object.prototype.toString.apply(value).slice(8, -1) 185 186 } 186 187 187 - function isSpecialProp(prop: Key, parentType: string) { 188 - return ( 189 - parentType.includes('Function') 190 - && typeof prop === 'string' 191 - && ['arguments', 'callee', 'caller', 'length', 'name'].includes(prop) 192 - ) 188 + function isReadonlyProp(object: unknown, prop: string | symbol) { 189 + if ( 190 + prop === 'arguments' 191 + || prop === 'caller' 192 + || prop === 'callee' 193 + || prop === 'name' 194 + || prop === 'length' 195 + ) { 196 + const typeName = getType(object) 197 + return ( 198 + typeName === 'Function' 199 + || typeName === 'AsyncFunction' 200 + || typeName === 'GeneratorFunction' 201 + || typeName === 'AsyncGeneratorFunction' 202 + ) 203 + } 204 + 205 + if ( 206 + prop === 'source' 207 + || prop === 'global' 208 + || prop === 'ignoreCase' 209 + || prop === 'multiline' 210 + ) { 211 + return getType(object) === 'RegExp' 212 + } 213 + 214 + return false 193 215 } 194 216 195 217 export interface GlobalConstructors { ··· 250 272 : (key: string | symbol) => collector.add(key) 251 273 Object.getOwnPropertyNames(obj).forEach(collect) 252 274 Object.getOwnPropertySymbols(obj).forEach(collect) 275 + } 276 + 277 + function collectFunctionProperties(prototype: any) { 278 + const properties = new Set<string | symbol>() 279 + collectOwnProperties(prototype, (prop) => { 280 + const descriptor = Object.getOwnPropertyDescriptor(prototype, prop) 281 + if (!descriptor || descriptor.get) { 282 + return 283 + } 284 + const type = getType(descriptor.value) 285 + if (type.includes('Function') && !isReadonlyProp(descriptor.value, prop)) { 286 + properties.add(prop) 287 + } 288 + }) 289 + return Array.from(properties) 253 290 }
+587 -719
packages/spy/src/index.ts
··· 1 - import type { SpyInternalImpl } from 'tinyspy' 2 - import * as tinyspy from 'tinyspy' 1 + import type { 2 + Classes, 3 + Constructable, 4 + Methods, 5 + Mock, 6 + MockConfig, 7 + MockContext, 8 + MockInstanceOption, 9 + MockProcedureContext, 10 + MockResult, 11 + MockReturnType, 12 + MockSettledResult, 13 + Procedure, 14 + Properties, 15 + } from './types' 3 16 4 - interface MockResultReturn<T> { 5 - type: 'return' 6 - /** 7 - * The value that was returned from the function. If function returned a Promise, then this will be a resolved value. 8 - */ 9 - value: T 10 - } 11 - interface MockResultIncomplete { 12 - type: 'incomplete' 13 - value: undefined 14 - } 15 - interface MockResultThrow { 16 - type: 'throw' 17 - /** 18 - * An error that was thrown during function execution. 19 - */ 20 - value: any 21 - } 22 - 23 - interface MockSettledResultFulfilled<T> { 24 - type: 'fulfilled' 25 - value: T 26 - } 27 - 28 - interface MockSettledResultRejected { 29 - type: 'rejected' 30 - value: any 31 - } 32 - 33 - export type MockResult<T> 34 - = | MockResultReturn<T> 35 - | MockResultThrow 36 - | MockResultIncomplete 37 - export type MockSettledResult<T> 38 - = | MockSettledResultFulfilled<T> 39 - | MockSettledResultRejected 40 - 41 - type MockParameters<T extends Procedure | Constructable> = T extends Constructable 42 - ? ConstructorParameters<T> 43 - : T extends Procedure 44 - ? Parameters<T> : never 45 - 46 - type MockReturnType<T extends Procedure | Constructable> = T extends Constructable 47 - ? void 48 - : T extends Procedure 49 - ? ReturnType<T> : never 50 - 51 - type MockFnContext<T extends Procedure | Constructable> = T extends Constructable 52 - ? InstanceType<T> 53 - : ThisParameterType<T> 54 - 55 - export interface MockContext<T extends Procedure | Constructable> { 56 - /** 57 - * This is an array containing all arguments for each call. One item of the array is the arguments of that call. 58 - * 59 - * @see https://vitest.dev/api/mock#mock-calls 60 - * @example 61 - * const fn = vi.fn() 62 - * 63 - * fn('arg1', 'arg2') 64 - * fn('arg3') 65 - * 66 - * fn.mock.calls === [ 67 - * ['arg1', 'arg2'], // first call 68 - * ['arg3'], // second call 69 - * ] 70 - */ 71 - calls: MockParameters<T>[] 72 - /** 73 - * This is an array containing all instances that were instantiated when mock was called with a `new` keyword. Note that this is an actual context (`this`) of the function, not a return value. 74 - * @see https://vitest.dev/api/mock#mock-instances 75 - */ 76 - instances: MockResultReturn<T>[] 77 - /** 78 - * An array of `this` values that were used during each call to the mock function. 79 - * @see https://vitest.dev/api/mock#mock-contexts 80 - */ 81 - contexts: MockFnContext<T>[] 82 - /** 83 - * The order of mock's execution. This returns an array of numbers which are shared between all defined mocks. 84 - * 85 - * @see https://vitest.dev/api/mock#mock-invocationcallorder 86 - * @example 87 - * const fn1 = vi.fn() 88 - * const fn2 = vi.fn() 89 - * 90 - * fn1() 91 - * fn2() 92 - * fn1() 93 - * 94 - * fn1.mock.invocationCallOrder === [1, 3] 95 - * fn2.mock.invocationCallOrder === [2] 96 - */ 97 - invocationCallOrder: number[] 98 - /** 99 - * This is an array containing all values that were `returned` from the function. 100 - * 101 - * The `value` property contains the returned value or thrown error. If the function returned a `Promise`, then `result` will always be `'return'` even if the promise was rejected. 102 - * 103 - * @see https://vitest.dev/api/mock#mock-results 104 - * @example 105 - * const fn = vi.fn() 106 - * .mockReturnValueOnce('result') 107 - * .mockImplementationOnce(() => { throw new Error('thrown error') }) 108 - * 109 - * const result = fn() 110 - * 111 - * try { 112 - * fn() 113 - * } 114 - * catch {} 115 - * 116 - * fn.mock.results === [ 117 - * { 118 - * type: 'return', 119 - * value: 'result', 120 - * }, 121 - * { 122 - * type: 'throw', 123 - * value: Error, 124 - * }, 125 - * ] 126 - */ 127 - results: MockResult<MockReturnType<T>>[] 128 - /** 129 - * An array containing all values that were `resolved` or `rejected` from the function. 130 - * 131 - * This array will be empty if the function was never resolved or rejected. 132 - * 133 - * @see https://vitest.dev/api/mock#mock-settledresults 134 - * @example 135 - * const fn = vi.fn().mockResolvedValueOnce('result') 136 - * 137 - * const result = fn() 138 - * 139 - * fn.mock.settledResults === [] 140 - * fn.mock.results === [ 141 - * { 142 - * type: 'return', 143 - * value: Promise<'result'>, 144 - * }, 145 - * ] 146 - * 147 - * await result 148 - * 149 - * fn.mock.settledResults === [ 150 - * { 151 - * type: 'fulfilled', 152 - * value: 'result', 153 - * }, 154 - * ] 155 - */ 156 - settledResults: MockSettledResult<Awaited<MockReturnType<T>>>[] 157 - /** 158 - * This contains the arguments of the last call. If spy wasn't called, will return `undefined`. 159 - * @see https://vitest.dev/api/mock#mock-lastcall 160 - */ 161 - lastCall: MockParameters<T> | undefined 162 - /** @internal */ 163 - _state: (state?: InternalState) => InternalState 164 - } 165 - 166 - interface InternalState { 167 - implementation: Procedure | Constructable | undefined 168 - onceImplementations: (Procedure | Constructable)[] 169 - implementationChangedTemporarily: boolean 170 - } 171 - 172 - type Procedure = (...args: any[]) => any 173 - // pick a single function type from function overloads, unions, etc... 174 - type NormalizedProcedure<T extends Procedure | Constructable> = T extends Constructable 175 - ? ({ 176 - new (...args: ConstructorParameters<T>): InstanceType<T> 177 - }) 178 - | ({ 179 - (this: InstanceType<T>, ...args: ConstructorParameters<T>): void 180 - }) 181 - : T extends Procedure 182 - ? (...args: Parameters<T>) => ReturnType<T> 183 - : never 184 - 185 - type Methods<T> = keyof { 186 - [K in keyof T as T[K] extends Procedure ? K : never]: T[K]; 187 - } 188 - type Properties<T> = { 189 - [K in keyof T]: T[K] extends Procedure ? never : K; 190 - }[keyof T] 191 - & (string | symbol) 192 - type Classes<T> = { 193 - [K in keyof T]: T[K] extends new (...args: any[]) => any ? K : never; 194 - }[keyof T] 195 - & (string | symbol) 196 - 197 - /* 198 - cf. https://typescript-eslint.io/rules/method-signature-style/ 199 - 200 - Typescript assignability is different between 201 - { foo: (f: T) => U } (this is "method-signature-style") 202 - and 203 - { foo(f: T): U } 204 - 205 - Jest uses the latter for `MockInstance.mockImplementation` etc... and it allows assignment such as: 206 - const boolFn: Jest.Mock<() => boolean> = jest.fn<() => true>(() => true) 207 - */ 208 - /* eslint-disable ts/method-signature-style */ 209 - export interface MockInstance<T extends Procedure | Constructable = Procedure> extends Disposable { 210 - /** 211 - * Use it to return the name assigned to the mock with the `.mockName(name)` method. By default, it will return `vi.fn()`. 212 - * @see https://vitest.dev/api/mock#getmockname 213 - */ 214 - getMockName(): string 215 - /** 216 - * Sets the internal mock name. This is useful for identifying the mock when an assertion fails. 217 - * @see https://vitest.dev/api/mock#mockname 218 - */ 219 - mockName(name: string): this 220 - /** 221 - * Current context of the mock. It stores information about all invocation calls, instances, and results. 222 - */ 223 - mock: MockContext<T> 224 - /** 225 - * Clears all information about every call. After calling it, all properties on `.mock` will return to their initial state. This method does not reset implementations. It is useful for cleaning up mocks between different assertions. 226 - * 227 - * To automatically call this method before each test, enable the [`clearMocks`](https://vitest.dev/config/#clearmocks) setting in the configuration. 228 - * @see https://vitest.dev/api/mock#mockclear 229 - */ 230 - mockClear(): this 231 - /** 232 - * Does what `mockClear` does and resets inner implementation to the original function. This also resets all "once" implementations. 233 - * 234 - * Note that resetting a mock from `vi.fn()` will set implementation to an empty function that returns `undefined`. 235 - * Resetting a mock from `vi.fn(impl)` will set implementation to `impl`. It is useful for completely resetting a mock to its default state. 236 - * 237 - * To automatically call this method before each test, enable the [`mockReset`](https://vitest.dev/config/#mockreset) setting in the configuration. 238 - * @see https://vitest.dev/api/mock#mockreset 239 - */ 240 - mockReset(): this 241 - /** 242 - * Does what `mockReset` does and restores original descriptors of spied-on objects. 243 - * 244 - * Note that restoring mock from `vi.fn()` will set implementation to an empty function that returns `undefined`. Restoring a `vi.fn(impl)` will restore implementation to `impl`. 245 - * @see https://vitest.dev/api/mock#mockrestore 246 - */ 247 - mockRestore(): void 248 - /** 249 - * Returns current permanent mock implementation if there is one. 250 - * 251 - * If mock was created with `vi.fn`, it will consider passed down method as a mock implementation. 252 - * 253 - * If mock was created with `vi.spyOn`, it will return `undefined` unless a custom implementation was provided. 254 - */ 255 - getMockImplementation(): NormalizedProcedure<T> | undefined 256 - /** 257 - * Accepts a function to be used as the mock implementation. TypeScript expects the arguments and return type to match those of the original function. 258 - * @see https://vitest.dev/api/mock#mockimplementation 259 - * @example 260 - * const increment = vi.fn().mockImplementation(count => count + 1); 261 - * expect(increment(3)).toBe(4); 262 - */ 263 - mockImplementation(fn: NormalizedProcedure<T>): this 264 - /** 265 - * Accepts a function to be used as the mock implementation. TypeScript expects the arguments and return type to match those of the original function. This method can be chained to produce different results for multiple function calls. 266 - * 267 - * When the mocked function runs out of implementations, it will invoke the default implementation set with `vi.fn(() => defaultValue)` or `.mockImplementation(() => defaultValue)` if they were called. 268 - * @see https://vitest.dev/api/mock#mockimplementationonce 269 - * @example 270 - * const fn = vi.fn(count => count).mockImplementationOnce(count => count + 1); 271 - * expect(fn(3)).toBe(4); 272 - * expect(fn(3)).toBe(3); 273 - */ 274 - mockImplementationOnce(fn: NormalizedProcedure<T>): this 275 - /** 276 - * Overrides the original mock implementation temporarily while the callback is being executed. 277 - * 278 - * Note that this method takes precedence over the [`mockImplementationOnce`](https://vitest.dev/api/mock#mockimplementationonce). 279 - * @see https://vitest.dev/api/mock#withimplementation 280 - * @example 281 - * const myMockFn = vi.fn(() => 'original') 282 - * 283 - * myMockFn.withImplementation(() => 'temp', () => { 284 - * myMockFn() // 'temp' 285 - * }) 286 - * 287 - * myMockFn() // 'original' 288 - */ 289 - withImplementation<T2>(fn: NormalizedProcedure<T>, cb: () => T2): T2 extends Promise<unknown> ? Promise<this> : this 290 - 291 - /** 292 - * Use this if you need to return the `this` context from the method without invoking the actual implementation. 293 - * @see https://vitest.dev/api/mock#mockreturnthis 294 - */ 295 - mockReturnThis(): this 296 - /** 297 - * Accepts a value that will be returned whenever the mock function is called. TypeScript will only accept values that match the return type of the original function. 298 - * @see https://vitest.dev/api/mock#mockreturnvalue 299 - * @example 300 - * const mock = vi.fn() 301 - * mock.mockReturnValue(42) 302 - * mock() // 42 303 - * mock.mockReturnValue(43) 304 - * mock() // 43 305 - */ 306 - mockReturnValue(value: MockReturnType<T>): this 307 - /** 308 - * Accepts a value that will be returned whenever the mock function is called. TypeScript will only accept values that match the return type of the original function. 309 - * 310 - * When the mocked function runs out of implementations, it will invoke the default implementation set with `vi.fn(() => defaultValue)` or `.mockImplementation(() => defaultValue)` if they were called. 311 - * @example 312 - * const myMockFn = vi 313 - * .fn() 314 - * .mockReturnValue('default') 315 - * .mockReturnValueOnce('first call') 316 - * .mockReturnValueOnce('second call') 317 - * 318 - * // 'first call', 'second call', 'default' 319 - * console.log(myMockFn(), myMockFn(), myMockFn()) 320 - */ 321 - mockReturnValueOnce(value: MockReturnType<T>): this 322 - /** 323 - * Accepts a value that will be resolved when the async function is called. TypeScript will only accept values that match the return type of the original function. 324 - * @example 325 - * const asyncMock = vi.fn().mockResolvedValue(42) 326 - * asyncMock() // Promise<42> 327 - */ 328 - mockResolvedValue(value: Awaited<MockReturnType<T>>): this 329 - /** 330 - * Accepts a value that will be resolved during the next function call. TypeScript will only accept values that match the return type of the original function. If chained, each consecutive call will resolve the specified value. 331 - * @example 332 - * const myMockFn = vi 333 - * .fn() 334 - * .mockResolvedValue('default') 335 - * .mockResolvedValueOnce('first call') 336 - * .mockResolvedValueOnce('second call') 337 - * 338 - * // Promise<'first call'>, Promise<'second call'>, Promise<'default'> 339 - * console.log(myMockFn(), myMockFn(), myMockFn()) 340 - */ 341 - mockResolvedValueOnce(value: Awaited<MockReturnType<T>>): this 342 - /** 343 - * Accepts an error that will be rejected when async function is called. 344 - * @example 345 - * const asyncMock = vi.fn().mockRejectedValue(new Error('Async error')) 346 - * await asyncMock() // throws Error<'Async error'> 347 - */ 348 - mockRejectedValue(error: unknown): this 349 - /** 350 - * Accepts a value that will be rejected during the next function call. If chained, each consecutive call will reject the specified value. 351 - * @example 352 - * const asyncMock = vi 353 - * .fn() 354 - * .mockResolvedValueOnce('first call') 355 - * .mockRejectedValueOnce(new Error('Async error')) 356 - * 357 - * await asyncMock() // first call 358 - * await asyncMock() // throws Error<'Async error'> 359 - */ 360 - mockRejectedValueOnce(error: unknown): this 361 - } 362 - /* eslint-enable ts/method-signature-style */ 363 - 364 - export interface Mock<T extends Procedure | Constructable = Procedure> extends MockInstance<T> { 365 - new (...args: MockParameters<T>): T extends Constructable ? InstanceType<T> : MockReturnType<T> 366 - (...args: MockParameters<T>): MockReturnType<T> 367 - } 368 - 369 - type PartialMaybePromise<T> = T extends Promise<Awaited<T>> 370 - ? Promise<Partial<Awaited<T>>> 371 - : Partial<T> 372 - 373 - export interface PartialMock<T extends Procedure = Procedure> 374 - extends MockInstance< 375 - (...args: Parameters<T>) => PartialMaybePromise<ReturnType<T>> 376 - > { 377 - new (...args: Parameters<T>): ReturnType<T> 378 - (...args: Parameters<T>): ReturnType<T> 379 - } 380 - 381 - export type MaybeMockedConstructor<T> = T extends new ( 382 - ...args: Array<any> 383 - ) => infer R 384 - ? Mock<(...args: ConstructorParameters<T>) => R> 385 - : T 386 - export type MockedFunction<T extends Procedure> = Mock<T> & { 387 - [K in keyof T]: T[K]; 388 - } 389 - export type PartiallyMockedFunction<T extends Procedure> = PartialMock<T> & { 390 - [K in keyof T]: T[K]; 391 - } 392 - export type MockedFunctionDeep<T extends Procedure> = Mock<T> 393 - & MockedObjectDeep<T> 394 - export type PartiallyMockedFunctionDeep<T extends Procedure> = PartialMock<T> 395 - & MockedObjectDeep<T> 396 - export type MockedObject<T> = MaybeMockedConstructor<T> & { 397 - [K in Methods<T>]: T[K] extends Procedure ? MockedFunction<T[K]> : T[K]; 398 - } & { [K in Properties<T>]: T[K] } 399 - export type MockedObjectDeep<T> = MaybeMockedConstructor<T> & { 400 - [K in Methods<T>]: T[K] extends Procedure ? MockedFunctionDeep<T[K]> : T[K]; 401 - } & { [K in Properties<T>]: MaybeMockedDeep<T[K]> } 402 - 403 - export type MaybeMockedDeep<T> = T extends Procedure 404 - ? MockedFunctionDeep<T> 405 - : T extends object 406 - ? MockedObjectDeep<T> 407 - : T 408 - 409 - export type MaybePartiallyMockedDeep<T> = T extends Procedure 410 - ? PartiallyMockedFunctionDeep<T> 411 - : T extends object 412 - ? MockedObjectDeep<T> 413 - : T 414 - 415 - export type MaybeMocked<T> = T extends Procedure 416 - ? MockedFunction<T> 417 - : T extends object 418 - ? MockedObject<T> 419 - : T 420 - 421 - export type MaybePartiallyMocked<T> = T extends Procedure 422 - ? PartiallyMockedFunction<T> 423 - : T extends object 424 - ? MockedObject<T> 425 - : T 426 - 427 - interface Constructable { 428 - new (...args: any[]): any 429 - } 430 - 431 - export type MockedClass<T extends Constructable> = MockInstance< 432 - (...args: ConstructorParameters<T>) => InstanceType<T> 433 - > & { 434 - prototype: T extends { prototype: any } ? Mocked<T['prototype']> : never 435 - } & T 436 - 437 - export type Mocked<T> = { 438 - [P in keyof T]: T[P] extends Procedure 439 - ? MockInstance<T[P]> 440 - : T[P] extends Constructable 441 - ? MockedClass<T[P]> 442 - : T[P]; 443 - } & T 444 - 445 - export const mocks: Set<MockInstance<any>> = new Set() 446 - 447 - export function isMockFunction(fn: any): fn is MockInstance { 17 + export function isMockFunction(fn: any): fn is Mock { 448 18 return ( 449 - typeof fn === 'function' && '_isMockFunction' in fn && fn._isMockFunction 19 + typeof fn === 'function' && '_isMockFunction' in fn && fn._isMockFunction === true 450 20 ) 451 21 } 452 22 453 - export function spyOn<T, S extends Properties<Required<T>>>( 454 - obj: T, 455 - methodName: S, 456 - accessType: 'get' 23 + const MOCK_RESTORE = new Set<() => void>() 24 + // Jest keeps the state in a separate WeakMap which is good for memory, 25 + // but it makes the state slower to access and return different values 26 + // if you stored it before calling `mockClear` where it will be recreated 27 + const REGISTERED_MOCKS = new Set<Mock>() 28 + const MOCK_CONFIGS = new WeakMap<Mock, MockConfig>() 29 + 30 + export function createMockInstance(options: MockInstanceOption = {}): Mock { 31 + const { 32 + originalImplementation, 33 + restore, 34 + mockImplementation, 35 + resetToMockImplementation, 36 + resetToMockName, 37 + } = options 38 + 39 + if (restore) { 40 + MOCK_RESTORE.add(restore) 41 + } 42 + 43 + const config = getDefaultConfig(originalImplementation) 44 + const state = getDefaultState() 45 + 46 + const mock = createMock({ 47 + config, 48 + state, 49 + ...options, 50 + }) 51 + // inherit the default name so it appears in snapshots and logs 52 + // this is used by `vi.spyOn()` for better debugging. 53 + // when `vi.fn()` is called, we just use the default string 54 + if (resetToMockName) { 55 + config.mockName = mock.name || 'vi.fn()' 56 + } 57 + MOCK_CONFIGS.set(mock, config) 58 + REGISTERED_MOCKS.add(mock) 59 + 60 + mock._isMockFunction = true 61 + mock.getMockImplementation = () => { 62 + // Jest only returns `config.mockImplementation` here, 63 + // but we think it makes sense to return what the next function will be called 64 + return config.onceMockImplementations[0] || config.mockImplementation 65 + } 66 + 67 + Object.defineProperty(mock, 'mock', { 68 + configurable: false, 69 + enumerable: true, 70 + writable: false, 71 + value: state, 72 + }) 73 + 74 + mock.mockImplementation = function mockImplementation(implementation) { 75 + config.mockImplementation = implementation 76 + return mock 77 + } 78 + 79 + mock.mockImplementationOnce = function mockImplementationOnce(implementation) { 80 + config.onceMockImplementations.push(implementation) 81 + return mock 82 + } 83 + 84 + mock.withImplementation = function withImplementation(implementation, callback) { 85 + const previousImplementation = config.mockImplementation 86 + const previousOnceImplementations = config.onceMockImplementations 87 + 88 + const reset = () => { 89 + config.mockImplementation = previousImplementation 90 + config.onceMockImplementations = previousOnceImplementations 91 + } 92 + 93 + config.mockImplementation = implementation 94 + config.onceMockImplementations = [] 95 + 96 + const returnValue = callback() 97 + 98 + if (typeof returnValue === 'object' && typeof (returnValue as Promise<any>)?.then === 'function') { 99 + return (returnValue as Promise<any>).then(() => { 100 + reset() 101 + return mock 102 + }) as any 103 + } 104 + else { 105 + reset() 106 + } 107 + return mock 108 + } 109 + 110 + mock.mockReturnThis = function mockReturnThis() { 111 + return mock.mockImplementation(function (this: any) { 112 + return this 113 + }) 114 + } 115 + 116 + mock.mockReturnValue = function mockReturnValue(value) { 117 + return mock.mockImplementation(() => value) 118 + } 119 + 120 + mock.mockReturnValueOnce = function mockReturnValueOnce(value) { 121 + return mock.mockImplementationOnce(() => value) 122 + } 123 + 124 + mock.mockResolvedValue = function mockResolvedValue(value) { 125 + return mock.mockImplementation(() => Promise.resolve(value)) 126 + } 127 + 128 + mock.mockResolvedValueOnce = function mockResolvedValueOnce(value) { 129 + return mock.mockImplementationOnce(() => Promise.resolve(value)) 130 + } 131 + 132 + mock.mockRejectedValue = function mockRejectedValue(value) { 133 + return mock.mockImplementation(() => Promise.reject(value)) 134 + } 135 + 136 + mock.mockRejectedValueOnce = function mockRejectedValueOnce(value) { 137 + return mock.mockImplementationOnce(() => Promise.reject(value)) 138 + } 139 + 140 + mock.mockClear = function mockClear() { 141 + state.calls = [] 142 + state.contexts = [] 143 + state.instances = [] 144 + state.invocationCallOrder = [] 145 + state.results = [] 146 + state.settledResults = [] 147 + return mock 148 + } 149 + 150 + mock.mockReset = function mockReset() { 151 + mock.mockClear() 152 + config.mockImplementation = resetToMockImplementation 153 + ? mockImplementation 154 + : undefined 155 + config.mockName = resetToMockName ? (mock.name || 'vi.fn()') : 'vi.fn()' 156 + config.onceMockImplementations = [] 157 + return mock 158 + } 159 + 160 + mock.mockRestore = function mockRestore() { 161 + mock.mockReset() 162 + return restore?.() 163 + } 164 + 165 + mock.mockName = function mockName(name: string) { 166 + if (typeof name === 'string') { 167 + config.mockName = name 168 + } 169 + return mock 170 + } 171 + 172 + mock.getMockName = function getMockName() { 173 + return config.mockName || 'vi.fn()' 174 + } 175 + 176 + if (Symbol.dispose) { 177 + mock[Symbol.dispose] = () => mock.mockRestore() 178 + } 179 + 180 + if (mockImplementation) { 181 + mock.mockImplementation(mockImplementation) 182 + } 183 + 184 + return mock 185 + } 186 + 187 + export function fn<T extends Procedure | Constructable = Procedure>( 188 + originalImplementation?: T, 189 + ): Mock<T> { 190 + return createMockInstance({ 191 + // we pass this down so getMockImplementation() always returns the value 192 + mockImplementation: originalImplementation, 193 + // special case so that .mockReset() resets the value to 194 + // the the originalImplementation instead of () => undefined 195 + resetToMockImplementation: true, 196 + }) as Mock<T> 197 + } 198 + 199 + export function spyOn<T extends object, S extends Properties<Required<T>>>( 200 + object: T, 201 + key: S, 202 + accessor: 'get' 457 203 ): Mock<() => T[S]> 458 - export function spyOn<T, G extends Properties<Required<T>>>( 459 - obj: T, 460 - methodName: G, 461 - accessType: 'set' 204 + export function spyOn<T extends object, G extends Properties<Required<T>>>( 205 + object: T, 206 + key: G, 207 + accessor: 'set' 462 208 ): Mock<(arg: T[G]) => void> 463 - export function spyOn<T, M extends Classes<Required<T>> | Methods<Required<T>>>( 464 - obj: T, 465 - methodName: M 209 + export function spyOn<T extends object, M extends Classes<Required<T>> | Methods<Required<T>>>( 210 + object: T, 211 + key: M 466 212 ): Required<T>[M] extends { new (...args: infer A): infer R } 467 213 ? Mock<{ new (...args: A): R }> 468 214 : T[M] extends Procedure 469 215 ? Mock<T[M]> 470 216 : never 471 - export function spyOn<T, K extends keyof T>( 472 - obj: T, 473 - method: K, 474 - accessType?: 'get' | 'set', 217 + export function spyOn<T extends object, K extends keyof T>( 218 + object: T, 219 + key: K, 220 + accessor?: 'get' | 'set', 475 221 ): Mock { 476 - const dictionary = { 477 - get: 'getter', 478 - set: 'setter', 479 - } as const 480 - const objMethod = accessType ? { [dictionary[accessType]]: method } : method 222 + assert( 223 + object != null, 224 + 'The vi.spyOn() function could not find an object to spy upon. The first argument must be defined.', 225 + ) 481 226 482 - let state: InternalState | undefined 227 + assert( 228 + typeof object === 'object' || typeof object === 'function', 229 + 'Vitest cannot spy on a primitive value.', 230 + ) 483 231 484 - const descriptor = getDescriptor(obj, method) 485 - const fn = descriptor && descriptor[accessType || 'value'] 232 + const [originalDescriptorObject, originalDescriptor] = getDescriptor(object, key) || [] 233 + assert( 234 + originalDescriptor || key in object, 235 + `The property "${String(key)}" is not defined on the ${typeof object}.`, 236 + ) 237 + let accessType: 'get' | 'set' | 'value' = accessor || 'value' 238 + let ssr = false 486 239 487 - // inherit implementations if it was already mocked 488 - if (isMockFunction(fn)) { 489 - state = fn.mock._state() 240 + // vite ssr support - actual function is stored inside a getter 241 + if ( 242 + accessType === 'value' 243 + && originalDescriptor 244 + && originalDescriptor.value == null 245 + && originalDescriptor.get 246 + ) { 247 + accessType = 'get' 248 + ssr = true 490 249 } 491 250 492 - try { 493 - const stub = tinyspy.internalSpyOn(obj, objMethod as any) 251 + let original: Procedure | undefined 494 252 495 - const spy = enhanceSpy(stub) as Mock 253 + if (originalDescriptor) { 254 + original = originalDescriptor[accessType] 255 + } 256 + else if (accessType !== 'value') { 257 + original = () => object[key] 258 + } 259 + else { 260 + original = object[key] as unknown as Procedure 261 + } 496 262 497 - if (state) { 498 - spy.mock._state(state) 263 + if (isMockFunction(original)) { 264 + return original 265 + } 266 + 267 + const reassign = (cb: any) => { 268 + const { value, ...desc } = originalDescriptor || { 269 + configurable: true, 270 + writable: true, 499 271 } 272 + if (accessType !== 'value') { 273 + delete desc.writable // getter/setter can't have writable attribute at all 274 + } 275 + ;(desc as PropertyDescriptor)[accessType] = cb 276 + Object.defineProperty(object, key, desc) 277 + } 500 278 501 - return spy 279 + const restore = () => { 280 + // if method is defined on the prototype, we can just remove it from 281 + // the current object instead of redefining a copy of it 282 + if (originalDescriptorObject !== object) { 283 + Reflect.deleteProperty(object, key) 284 + } 285 + else if (originalDescriptor && !original) { 286 + Object.defineProperty(object, key, originalDescriptor) 287 + } 288 + else { 289 + reassign(original) 290 + } 291 + } 292 + 293 + const mock = createMockInstance({ 294 + restore, 295 + originalImplementation: ssr && original ? original() : original, 296 + resetToMockName: true, 297 + }) 298 + 299 + try { 300 + reassign( 301 + ssr 302 + ? () => mock 303 + : mock, 304 + ) 502 305 } 503 306 catch (error) { 504 307 if ( 505 308 error instanceof TypeError 506 309 && Symbol.toStringTag 507 - && (obj as any)[Symbol.toStringTag] === 'Module' 310 + && (object as any)[Symbol.toStringTag] === 'Module' 508 311 && (error.message.includes('Cannot redefine property') 509 312 || error.message.includes('Cannot replace module namespace') 510 313 || error.message.includes('can\'t redefine non-configurable property')) 511 314 ) { 512 315 throw new TypeError( 513 - `Cannot spy on export "${String(objMethod)}". Module namespace is not configurable in ESM. See: https://vitest.dev/guide/browser/#limitations`, 316 + `Cannot spy on export "${String(key)}". Module namespace is not configurable in ESM. See: https://vitest.dev/guide/browser/#limitations`, 514 317 { cause: error }, 515 318 ) 516 319 } 517 320 518 321 throw error 519 322 } 323 + 324 + return mock 520 325 } 521 326 522 - let callOrder = 0 523 - 524 - function enhanceSpy<T extends Procedure | Constructable>( 525 - spy: SpyInternalImpl<MockParameters<T>, MockReturnType<T>>, 526 - ): MockInstance<T> { 527 - type TReturns = MockReturnType<T> 528 - 529 - const stub = spy as unknown as MockInstance<T> 530 - 531 - let implementation: NormalizedProcedure<T> | undefined 532 - 533 - let onceImplementations: NormalizedProcedure<T>[] = [] 534 - let implementationChangedTemporarily = false 535 - 536 - let instances: any[] = [] 537 - let contexts: any[] = [] 538 - let invocations: number[] = [] 539 - 540 - const state = tinyspy.getInternalState(spy) 541 - 542 - const mockContext: MockContext<T> = { 543 - get calls() { 544 - return state.calls as MockParameters<T>[] 545 - }, 546 - get contexts() { 547 - return contexts 548 - }, 549 - get instances() { 550 - return instances 551 - }, 552 - get invocationCallOrder() { 553 - return invocations 554 - }, 555 - get results() { 556 - return state.results.map(([callType, value]) => { 557 - const type 558 - = callType === 'error' ? ('throw' as const) : ('return' as const) 559 - return { type, value } 560 - }) 561 - }, 562 - get settledResults() { 563 - return state.resolves.map(([callType, value]) => { 564 - const type 565 - = callType === 'error' ? ('rejected' as const) : ('fulfilled' as const) 566 - return { type, value } 567 - }) 568 - }, 569 - get lastCall() { 570 - return state.calls[state.calls.length - 1] as MockParameters<T> 571 - }, 572 - _state(state) { 573 - if (state) { 574 - implementation = state.implementation as NormalizedProcedure<T> 575 - onceImplementations = state.onceImplementations as NormalizedProcedure<T>[] 576 - implementationChangedTemporarily = state.implementationChangedTemporarily 577 - } 578 - return { 579 - implementation, 580 - onceImplementations, 581 - implementationChangedTemporarily, 582 - } 583 - }, 584 - } 585 - 586 - function mockCall(this: unknown, ...args: any) { 587 - if (!new.target) { 588 - instances.push(this) 589 - contexts.push(this) 590 - } 591 - invocations.push(++callOrder) 592 - const impl = implementationChangedTemporarily 593 - ? implementation! 594 - : onceImplementations.shift() 595 - || implementation 596 - || state.getOriginal() 597 - || function () {} 598 - if (new.target) { 599 - try { 600 - const result = Reflect.construct(impl, args, new.target) 601 - instances.push(result) 602 - contexts.push(result) 603 - return result 604 - } 605 - catch (error) { 606 - instances.push(undefined) 607 - contexts.push(undefined) 608 - 609 - if (error instanceof TypeError && error.message.includes('is not a constructor')) { 610 - throw new TypeError(`The spy implementation did not use 'function' or 'class', see https://vitest.dev/api/vi#vi-spyon for examples.`, { 611 - cause: error, 612 - }) 613 - } 614 - 615 - throw error 616 - } 617 - } 618 - return (impl as Procedure).apply(this, args) 619 - } 620 - 621 - let name: string = (stub as any).name 622 - 623 - stub.getMockName = () => name || 'vi.fn()' 624 - stub.mockName = (n) => { 625 - name = n 626 - return stub 627 - } 628 - 629 - stub.mockClear = () => { 630 - state.reset() 631 - instances = [] 632 - contexts = [] 633 - invocations = [] 634 - return stub 635 - } 636 - 637 - stub.mockReset = () => { 638 - stub.mockClear() 639 - implementation = undefined 640 - onceImplementations = [] 641 - return stub 642 - } 643 - 644 - stub.mockRestore = () => { 645 - stub.mockReset() 646 - state.restore() 647 - return stub 648 - } 649 - 650 - if (Symbol.dispose) { 651 - stub[Symbol.dispose] = () => stub.mockRestore() 652 - } 653 - 654 - stub.getMockImplementation = () => 655 - implementationChangedTemporarily ? implementation : (onceImplementations.at(0) || implementation) 656 - stub.mockImplementation = (fn: NormalizedProcedure<T>) => { 657 - implementation = fn 658 - state.willCall(mockCall) 659 - return stub 660 - } 661 - 662 - stub.mockImplementationOnce = (fn: NormalizedProcedure<T>) => { 663 - onceImplementations.push(fn) 664 - return stub 665 - } 666 - 667 - function withImplementation(fn: NormalizedProcedure<T>, cb: () => void): MockInstance<T> 668 - function withImplementation(fn: NormalizedProcedure<T>, cb: () => Promise<void>): Promise<MockInstance<T>> 669 - function withImplementation(fn: NormalizedProcedure<T>, cb: () => void | Promise<void>): MockInstance<T> | Promise<MockInstance<T>> { 670 - const originalImplementation = implementation 671 - 672 - implementation = fn 673 - state.willCall(mockCall) 674 - implementationChangedTemporarily = true 675 - 676 - const reset = () => { 677 - implementation = originalImplementation 678 - implementationChangedTemporarily = false 679 - } 680 - 681 - const result = cb() 682 - 683 - if (typeof result === 'object' && result && typeof result.then === 'function') { 684 - return result.then(() => { 685 - reset() 686 - return stub 687 - }) 688 - } 689 - 690 - reset() 691 - 692 - return stub 693 - } 694 - 695 - stub.withImplementation = withImplementation 696 - 697 - stub.mockReturnThis = () => 698 - stub.mockImplementation(function (this: MockFnContext<T>) { 699 - return this 700 - } as NormalizedProcedure<T>) 701 - 702 - stub.mockReturnValue = (val: TReturns) => stub.mockImplementation(function () { 703 - return val 704 - } as NormalizedProcedure<T>) 705 - stub.mockReturnValueOnce = (val: TReturns) => stub.mockImplementationOnce(function () { 706 - return val 707 - } as NormalizedProcedure<T>) 708 - 709 - stub.mockResolvedValue = (val: Awaited<TReturns>) => 710 - stub.mockImplementation(function () { 711 - return Promise.resolve(val) 712 - } as NormalizedProcedure<T>) 713 - 714 - stub.mockResolvedValueOnce = (val: Awaited<TReturns>) => 715 - stub.mockImplementationOnce(function () { 716 - return Promise.resolve(val) 717 - } as NormalizedProcedure<T>) 718 - 719 - stub.mockRejectedValue = (val: unknown) => 720 - stub.mockImplementation(function () { 721 - return Promise.reject(val) 722 - } as NormalizedProcedure<T>) 723 - 724 - stub.mockRejectedValueOnce = (val: unknown) => 725 - stub.mockImplementationOnce(function () { 726 - return Promise.reject(val) 727 - } as NormalizedProcedure<T>) 728 - 729 - Object.defineProperty(stub, 'mock', { 730 - get: () => mockContext, 731 - }) 732 - 733 - state.willCall(mockCall) 734 - 735 - mocks.add(stub) 736 - 737 - return stub as any 738 - } 739 - 740 - export function fn<T extends Procedure | Constructable = Procedure>( 741 - implementation?: T, 742 - ): Mock<T> { 743 - const inernalSpy = tinyspy.internalSpyOn({ 744 - spy: implementation || function spy() {} as T, 745 - }, 'spy') 746 - const enhancedSpy = enhanceSpy(inernalSpy) 747 - if (implementation) { 748 - enhancedSpy.mockImplementation(implementation) 749 - } 750 - 751 - return enhancedSpy as any 752 - } 753 - 754 - function getDescriptor( 755 - obj: any, 756 - method: string | symbol | number, 757 - ): PropertyDescriptor | undefined { 327 + function getDescriptor(obj: any, method: string | symbol | number): [any, PropertyDescriptor] | undefined { 758 328 const objDescriptor = Object.getOwnPropertyDescriptor(obj, method) 759 329 if (objDescriptor) { 760 - return objDescriptor 330 + return [obj, objDescriptor] 761 331 } 762 332 let currentProto = Object.getPrototypeOf(obj) 763 333 while (currentProto !== null) { 764 334 const descriptor = Object.getOwnPropertyDescriptor(currentProto, method) 765 335 if (descriptor) { 766 - return descriptor 336 + return [currentProto, descriptor] 767 337 } 768 338 currentProto = Object.getPrototypeOf(currentProto) 769 339 } 770 340 } 341 + 342 + function assert(condition: any, message: string): asserts condition { 343 + if (!condition) { 344 + throw new Error(message) 345 + } 346 + } 347 + 348 + let invocationCallCounter = 1 349 + 350 + function createMock( 351 + { 352 + state, 353 + config, 354 + name: mockName, 355 + prototypeState, 356 + prototypeConfig, 357 + keepMembersImplementation, 358 + prototypeMembers = [], 359 + }: MockInstanceOption & { 360 + state: MockContext 361 + config: MockConfig 362 + }, 363 + ) { 364 + const original = config.mockOriginal 365 + const name = (mockName || original?.name || 'Mock') as string 366 + const namedObject: Record<string, Mock<Procedure | Constructable>> = { 367 + // to keep the name of the function intact 368 + [name]: (function (this: any, ...args: any[]) { 369 + registerCalls(args, state, prototypeState) 370 + registerInvocationOrder(invocationCallCounter++, state, prototypeState) 371 + 372 + const result = { 373 + type: 'incomplete', 374 + value: undefined, 375 + } as MockResult<Procedure> 376 + 377 + const settledResult = { 378 + type: 'incomplete', 379 + value: undefined, 380 + } as MockSettledResult<Procedure> 381 + 382 + registerResult(result, state, prototypeState) 383 + registerSettledResult(settledResult, state, prototypeState) 384 + 385 + const context = new.target ? undefined : this 386 + const [instanceIndex, instancePrototypeIndex] = registerInstance(context, state, prototypeState) 387 + const [contextIndex, contextPrototypeIndex] = registerContext(context, state, prototypeState) 388 + 389 + const implementation: Procedure | Constructable 390 + = config.onceMockImplementations.shift() 391 + || config.mockImplementation 392 + || prototypeConfig?.onceMockImplementations.shift() 393 + || prototypeConfig?.mockImplementation 394 + || original 395 + || function () {} 396 + 397 + let returnValue 398 + let thrownValue 399 + let didThrow = false 400 + 401 + try { 402 + if (new.target) { 403 + returnValue = Reflect.construct(implementation, args, new.target) 404 + 405 + // jest calls this before the implementation, but we have to resolve this _after_ 406 + // because we cannot do it before the `Reflect.construct` called the custom implementation. 407 + // fortunetly, the constructor is always an empty functon because `prototypeMethods` 408 + // are only used by the automocker, so this doesn't matter 409 + for (const prop of prototypeMembers) { 410 + const prototypeMock = returnValue[prop] 411 + const isMock = isMockFunction(prototypeMock) 412 + const prototypeState = isMock ? prototypeMock.mock : undefined 413 + const prototypeConfig = isMock ? MOCK_CONFIGS.get(prototypeMock) : undefined 414 + returnValue[prop] = createMockInstance({ 415 + originalImplementation: keepMembersImplementation 416 + ? prototypeConfig?.mockOriginal 417 + : undefined, 418 + prototypeState, 419 + prototypeConfig, 420 + keepMembersImplementation, 421 + }) 422 + } 423 + } 424 + else { 425 + returnValue = (implementation as Procedure).apply(this, args) 426 + } 427 + } 428 + catch (error: any) { 429 + thrownValue = error 430 + didThrow = true 431 + if (error instanceof TypeError && error.message.includes('is not a constructor')) { 432 + console.warn(`[vitest] The ${namedObject[name].getMockName()} mock did not use 'function' or 'class' in its implementation, see https://vitest.dev/api/vi#vi-spyon for examples.`) 433 + } 434 + throw error 435 + } 436 + finally { 437 + if (didThrow) { 438 + result.type = 'throw' 439 + result.value = thrownValue 440 + 441 + settledResult.type = 'rejected' 442 + settledResult.value = thrownValue 443 + } 444 + else { 445 + result.type = 'return' 446 + result.value = returnValue 447 + 448 + if (new.target) { 449 + state.contexts[contextIndex - 1] = returnValue 450 + state.instances[instanceIndex - 1] = returnValue 451 + 452 + if (contextPrototypeIndex != null && prototypeState) { 453 + prototypeState.contexts[contextPrototypeIndex - 1] = returnValue 454 + } 455 + if (instancePrototypeIndex != null && prototypeState) { 456 + prototypeState.instances[instancePrototypeIndex - 1] = returnValue 457 + } 458 + } 459 + 460 + if (returnValue instanceof Promise) { 461 + returnValue.then( 462 + (settledValue) => { 463 + settledResult.type = 'fulfilled' 464 + settledResult.value = settledValue 465 + }, 466 + (rejectedValue) => { 467 + settledResult.type = 'rejected' 468 + settledResult.value = rejectedValue 469 + }, 470 + ) 471 + } 472 + else { 473 + settledResult.type = 'fulfilled' 474 + settledResult.value = returnValue 475 + } 476 + } 477 + } 478 + 479 + return returnValue 480 + }) as Mock, 481 + } 482 + if (original) { 483 + copyOriginalStaticProperties(namedObject[name], original) 484 + } 485 + return namedObject[name] 486 + } 487 + 488 + function registerCalls(args: unknown[], state: MockContext, prototypeState?: MockContext) { 489 + state.calls.push(args) 490 + prototypeState?.calls.push(args) 491 + } 492 + 493 + function registerInvocationOrder(order: number, state: MockContext, prototypeState?: MockContext) { 494 + state.invocationCallOrder.push(order) 495 + prototypeState?.invocationCallOrder.push(order) 496 + } 497 + 498 + function registerResult(result: MockResult<Procedure>, state: MockContext, prototypeState?: MockContext) { 499 + state.results.push(result) 500 + prototypeState?.results.push(result) 501 + } 502 + 503 + function registerSettledResult(result: MockSettledResult<Procedure>, state: MockContext, prototypeState?: MockContext) { 504 + state.settledResults.push(result) 505 + prototypeState?.settledResults.push(result) 506 + } 507 + 508 + function registerInstance(instance: MockReturnType<Procedure>, state: MockContext, prototypeState?: MockContext) { 509 + const instanceIndex = state.instances.push(instance) 510 + const instancePrototypeIndex = prototypeState?.instances.push(instance) 511 + return [instanceIndex, instancePrototypeIndex] as const 512 + } 513 + 514 + function registerContext(context: MockProcedureContext<Procedure>, state: MockContext, prototypeState?: MockContext) { 515 + const contextIndex = state.contexts.push(context) 516 + const contextPrototypeIndex = prototypeState?.contexts.push(context) 517 + return [contextIndex, contextPrototypeIndex] as const 518 + } 519 + 520 + function copyOriginalStaticProperties(mock: Mock, original: Procedure | Constructable) { 521 + const { properties, descriptors } = getAllProperties(original) 522 + 523 + for (const key of properties) { 524 + const descriptor = descriptors[key]! 525 + const mockDescriptor = getDescriptor(mock, key) 526 + if (mockDescriptor) { 527 + continue 528 + } 529 + 530 + Object.defineProperty(mock, key, descriptor) 531 + } 532 + } 533 + 534 + const ignoreProperties = new Set<string | symbol>([ 535 + 'length', 536 + 'name', 537 + 'prototype', 538 + Symbol.for('nodejs.util.promisify.custom'), 539 + ]) 540 + 541 + function getAllProperties(original: Procedure | Constructable) { 542 + const properties = new Set<string | symbol>() 543 + const descriptors: Record<string | symbol, PropertyDescriptor | undefined> 544 + = {} 545 + while ( 546 + original 547 + && original !== Object.prototype 548 + && original !== Function.prototype 549 + ) { 550 + const ownProperties = [ 551 + ...Object.getOwnPropertyNames(original), 552 + ...Object.getOwnPropertySymbols(original), 553 + ] 554 + for (const prop of ownProperties) { 555 + if (descriptors[prop] || ignoreProperties.has(prop)) { 556 + continue 557 + } 558 + properties.add(prop) 559 + descriptors[prop] = Object.getOwnPropertyDescriptor(original, prop) 560 + } 561 + original = Object.getPrototypeOf(original) 562 + } 563 + return { 564 + properties, 565 + descriptors, 566 + } 567 + } 568 + 569 + function getDefaultConfig(original?: Procedure | Constructable): MockConfig { 570 + return { 571 + mockImplementation: undefined, 572 + mockOriginal: original, 573 + mockName: 'vi.fn()', 574 + onceMockImplementations: [], 575 + } 576 + } 577 + 578 + function getDefaultState(): MockContext { 579 + const state = { 580 + calls: [], 581 + contexts: [], 582 + instances: [], 583 + invocationCallOrder: [], 584 + settledResults: [], 585 + results: [], 586 + get lastCall() { 587 + return state.calls.at(-1) 588 + }, 589 + } 590 + return state 591 + } 592 + 593 + export function restoreAllMocks(): void { 594 + for (const restore of MOCK_RESTORE) { 595 + restore() 596 + } 597 + MOCK_RESTORE.clear() 598 + } 599 + 600 + export function clearAllMocks(): void { 601 + REGISTERED_MOCKS.forEach(mock => mock.mockClear()) 602 + } 603 + 604 + export function resetAllMocks(): void { 605 + REGISTERED_MOCKS.forEach(mock => mock.mockReset()) 606 + } 607 + 608 + export type { 609 + MaybeMocked, 610 + MaybeMockedConstructor, 611 + MaybeMockedDeep, 612 + MaybePartiallyMocked, 613 + MaybePartiallyMockedDeep, 614 + Mock, 615 + MockContext, 616 + Mocked, 617 + MockedClass, 618 + MockedFunction, 619 + MockedFunctionDeep, 620 + MockedObject, 621 + MockedObjectDeep, 622 + MockInstance, 623 + MockInstanceOption, 624 + MockParameters, 625 + MockProcedureContext, 626 + MockResult, 627 + MockResultIncomplete, 628 + MockResultReturn, 629 + MockResultThrow, 630 + MockReturnType, 631 + MockSettledResult, 632 + MockSettledResultFulfilled, 633 + MockSettledResultIncomplete, 634 + MockSettledResultRejected, 635 + PartiallyMockedFunction, 636 + PartiallyMockedFunctionDeep, 637 + PartialMock, 638 + } from './types'
+463
packages/spy/src/types.ts
··· 1 + export interface MockResultReturn<T> { 2 + type: 'return' 3 + /** 4 + * The value that was returned from the function. If function returned a Promise, then this will be a resolved value. 5 + */ 6 + value: T 7 + } 8 + export interface MockResultIncomplete { 9 + type: 'incomplete' 10 + value: undefined 11 + } 12 + export interface MockResultThrow { 13 + type: 'throw' 14 + /** 15 + * An error that was thrown during function execution. 16 + */ 17 + value: any 18 + } 19 + 20 + export interface MockSettledResultIncomplete { 21 + type: 'incomplete' 22 + value: undefined 23 + } 24 + 25 + export interface MockSettledResultFulfilled<T> { 26 + type: 'fulfilled' 27 + value: T 28 + } 29 + 30 + export interface MockSettledResultRejected { 31 + type: 'rejected' 32 + value: any 33 + } 34 + 35 + export type MockResult<T> 36 + = | MockResultReturn<T> 37 + | MockResultThrow 38 + | MockResultIncomplete 39 + export type MockSettledResult<T> 40 + = | MockSettledResultFulfilled<T> 41 + | MockSettledResultRejected 42 + | MockSettledResultIncomplete 43 + 44 + export type MockParameters<T extends Procedure | Constructable> = T extends Constructable 45 + ? ConstructorParameters<T> 46 + : T extends Procedure 47 + ? Parameters<T> : never 48 + 49 + export type MockReturnType<T extends Procedure | Constructable> = T extends Constructable 50 + ? void 51 + : T extends Procedure 52 + ? ReturnType<T> : never 53 + 54 + export type MockProcedureContext<T extends Procedure | Constructable> = T extends Constructable 55 + ? InstanceType<T> 56 + : ThisParameterType<T> 57 + 58 + export interface MockContext<T extends Procedure | Constructable = Procedure> { 59 + /** 60 + * This is an array containing all arguments for each call. One item of the array is the arguments of that call. 61 + * 62 + * @see https://vitest.dev/api/mock#mock-calls 63 + * @example 64 + * const fn = vi.fn() 65 + * 66 + * fn('arg1', 'arg2') 67 + * fn('arg3') 68 + * 69 + * fn.mock.calls === [ 70 + * ['arg1', 'arg2'], // first call 71 + * ['arg3'], // second call 72 + * ] 73 + */ 74 + calls: MockParameters<T>[] 75 + /** 76 + * This is an array containing all instances that were instantiated when mock was called with a `new` keyword. Note that this is an actual context (`this`) of the function, not a return value. 77 + * @see https://vitest.dev/api/mock#mock-instances 78 + */ 79 + instances: MockReturnType<T>[] 80 + /** 81 + * An array of `this` values that were used during each call to the mock function. 82 + * @see https://vitest.dev/api/mock#mock-contexts 83 + */ 84 + contexts: MockProcedureContext<T>[] 85 + /** 86 + * The order of mock's execution. This returns an array of numbers which are shared between all defined mocks. 87 + * 88 + * @see https://vitest.dev/api/mock#mock-invocationcallorder 89 + * @example 90 + * const fn1 = vi.fn() 91 + * const fn2 = vi.fn() 92 + * 93 + * fn1() 94 + * fn2() 95 + * fn1() 96 + * 97 + * fn1.mock.invocationCallOrder === [1, 3] 98 + * fn2.mock.invocationCallOrder === [2] 99 + */ 100 + invocationCallOrder: number[] 101 + /** 102 + * This is an array containing all values that were `returned` from the function. 103 + * 104 + * The `value` property contains the returned value or thrown error. If the function returned a `Promise`, then `result` will always be `'return'` even if the promise was rejected. 105 + * 106 + * @see https://vitest.dev/api/mock#mock-results 107 + * @example 108 + * const fn = vi.fn() 109 + * .mockReturnValueOnce('result') 110 + * .mockImplementationOnce(() => { throw new Error('thrown error') }) 111 + * 112 + * const result = fn() 113 + * 114 + * try { 115 + * fn() 116 + * } 117 + * catch {} 118 + * 119 + * fn.mock.results === [ 120 + * { 121 + * type: 'return', 122 + * value: 'result', 123 + * }, 124 + * { 125 + * type: 'throw', 126 + * value: Error, 127 + * }, 128 + * ] 129 + */ 130 + results: MockResult<MockReturnType<T>>[] 131 + /** 132 + * An array containing all values that were `resolved` or `rejected` from the function. 133 + * 134 + * This array will be empty if the function was never resolved or rejected. 135 + * 136 + * @see https://vitest.dev/api/mock#mock-settledresults 137 + * @example 138 + * const fn = vi.fn().mockResolvedValueOnce('result') 139 + * 140 + * const result = fn() 141 + * 142 + * fn.mock.settledResults === [ 143 + * { 144 + * type: 'incomplete', 145 + * value: undefined, 146 + * } 147 + * ] 148 + * fn.mock.results === [ 149 + * { 150 + * type: 'return', 151 + * value: Promise<'result'>, 152 + * }, 153 + * ] 154 + * 155 + * await result 156 + * 157 + * fn.mock.settledResults === [ 158 + * { 159 + * type: 'fulfilled', 160 + * value: 'result', 161 + * }, 162 + * ] 163 + */ 164 + settledResults: MockSettledResult<Awaited<MockReturnType<T>>>[] 165 + /** 166 + * This contains the arguments of the last call. If spy wasn't called, will return `undefined`. 167 + * @see https://vitest.dev/api/mock#mock-lastcall 168 + */ 169 + lastCall: MockParameters<T> | undefined 170 + } 171 + 172 + export type Procedure = (...args: any[]) => any 173 + // pick a single function type from function overloads, unions, etc... 174 + export type NormalizedProcedure<T extends Procedure | Constructable> = T extends Constructable 175 + ? ({ 176 + new (...args: ConstructorParameters<T>): InstanceType<T> 177 + }) 178 + | ({ 179 + (this: InstanceType<T>, ...args: ConstructorParameters<T>): void 180 + }) 181 + : T extends Procedure 182 + ? (...args: Parameters<T>) => ReturnType<T> 183 + : never 184 + 185 + export type Methods<T> = keyof { 186 + [K in keyof T as T[K] extends Procedure ? K : never]: T[K]; 187 + } 188 + export type Properties<T> = { 189 + [K in keyof T]: T[K] extends Procedure ? never : K; 190 + }[keyof T] 191 + & (string | symbol) 192 + export type Classes<T> = { 193 + [K in keyof T]: T[K] extends new (...args: any[]) => any ? K : never; 194 + }[keyof T] 195 + & (string | symbol) 196 + 197 + /* 198 + cf. https://typescript-eslint.io/rules/method-signature-style/ 199 + 200 + Typescript assignability is different between 201 + { foo: (f: T) => U } (this is "method-signature-style") 202 + and 203 + { foo(f: T): U } 204 + 205 + Jest uses the latter for `MockInstance.mockImplementation` etc... and it allows assignment such as: 206 + const boolFn: Jest.Mock<() => boolean> = jest.fn<() => true>(() => true) 207 + */ 208 + /* eslint-disable ts/method-signature-style */ 209 + export interface MockInstance<T extends Procedure | Constructable = Procedure> extends Disposable { 210 + /** 211 + * Use it to return the name assigned to the mock with the `.mockName(name)` method. By default, it will return `vi.fn()`. 212 + * @see https://vitest.dev/api/mock#getmockname 213 + */ 214 + getMockName(): string 215 + /** 216 + * Sets the internal mock name. This is useful for identifying the mock when an assertion fails. 217 + * @see https://vitest.dev/api/mock#mockname 218 + */ 219 + mockName(name: string): this 220 + /** 221 + * Current context of the mock. It stores information about all invocation calls, instances, and results. 222 + */ 223 + mock: MockContext<T> 224 + /** 225 + * Clears all information about every call. After calling it, all properties on `.mock` will return to their initial state. This method does not reset implementations. It is useful for cleaning up mocks between different assertions. 226 + * 227 + * To automatically call this method before each test, enable the [`clearMocks`](https://vitest.dev/config/#clearmocks) setting in the configuration. 228 + * @see https://vitest.dev/api/mock#mockclear 229 + */ 230 + mockClear(): this 231 + /** 232 + * Does what `mockClear` does and resets inner implementation to the original function. This also resets all "once" implementations. 233 + * 234 + * Note that resetting a mock from `vi.fn()` will set implementation to an empty function that returns `undefined`. 235 + * Resetting a mock from `vi.fn(impl)` will set implementation to `impl`. It is useful for completely resetting a mock to its default state. 236 + * 237 + * To automatically call this method before each test, enable the [`mockReset`](https://vitest.dev/config/#mockreset) setting in the configuration. 238 + * @see https://vitest.dev/api/mock#mockreset 239 + */ 240 + mockReset(): this 241 + /** 242 + * Does what `mockReset` does and restores original descriptors of spied-on objects. 243 + * @see https://vitest.dev/api/mock#mockrestore 244 + */ 245 + mockRestore(): void 246 + /** 247 + * Returns current permanent mock implementation if there is one. 248 + * 249 + * If mock was created with `vi.fn`, it will consider passed down method as a mock implementation. 250 + * 251 + * If mock was created with `vi.spyOn`, it will return `undefined` unless a custom implementation was provided. 252 + */ 253 + getMockImplementation(): NormalizedProcedure<T> | undefined 254 + /** 255 + * Accepts a function to be used as the mock implementation. TypeScript expects the arguments and return type to match those of the original function. 256 + * @see https://vitest.dev/api/mock#mockimplementation 257 + * @example 258 + * const increment = vi.fn().mockImplementation(count => count + 1); 259 + * expect(increment(3)).toBe(4); 260 + */ 261 + mockImplementation(fn: NormalizedProcedure<T>): this 262 + /** 263 + * Accepts a function to be used as the mock implementation. TypeScript expects the arguments and return type to match those of the original function. This method can be chained to produce different results for multiple function calls. 264 + * 265 + * When the mocked function runs out of implementations, it will invoke the default implementation set with `vi.fn(() => defaultValue)` or `.mockImplementation(() => defaultValue)` if they were called. 266 + * @see https://vitest.dev/api/mock#mockimplementationonce 267 + * @example 268 + * const fn = vi.fn(count => count).mockImplementationOnce(count => count + 1); 269 + * expect(fn(3)).toBe(4); 270 + * expect(fn(3)).toBe(3); 271 + */ 272 + mockImplementationOnce(fn: NormalizedProcedure<T>): this 273 + /** 274 + * Overrides the original mock implementation temporarily while the callback is being executed. 275 + * 276 + * Note that this method takes precedence over the [`mockImplementationOnce`](https://vitest.dev/api/mock#mockimplementationonce). 277 + * @see https://vitest.dev/api/mock#withimplementation 278 + * @example 279 + * const myMockFn = vi.fn(() => 'original') 280 + * 281 + * myMockFn.withImplementation(() => 'temp', () => { 282 + * myMockFn() // 'temp' 283 + * }) 284 + * 285 + * myMockFn() // 'original' 286 + */ 287 + withImplementation<T2>(fn: NormalizedProcedure<T>, cb: () => T2): T2 extends Promise<unknown> ? Promise<this> : this 288 + 289 + /** 290 + * Use this if you need to return the `this` context from the method without invoking the actual implementation. 291 + * @see https://vitest.dev/api/mock#mockreturnthis 292 + */ 293 + mockReturnThis(): this 294 + /** 295 + * Accepts a value that will be returned whenever the mock function is called. TypeScript will only accept values that match the return type of the original function. 296 + * @see https://vitest.dev/api/mock#mockreturnvalue 297 + * @example 298 + * const mock = vi.fn() 299 + * mock.mockReturnValue(42) 300 + * mock() // 42 301 + * mock.mockReturnValue(43) 302 + * mock() // 43 303 + */ 304 + mockReturnValue(value: MockReturnType<T>): this 305 + /** 306 + * Accepts a value that will be returned whenever the mock function is called. TypeScript will only accept values that match the return type of the original function. 307 + * 308 + * When the mocked function runs out of implementations, it will invoke the default implementation set with `vi.fn(() => defaultValue)` or `.mockImplementation(() => defaultValue)` if they were called. 309 + * @example 310 + * const myMockFn = vi 311 + * .fn() 312 + * .mockReturnValue('default') 313 + * .mockReturnValueOnce('first call') 314 + * .mockReturnValueOnce('second call') 315 + * 316 + * // 'first call', 'second call', 'default' 317 + * console.log(myMockFn(), myMockFn(), myMockFn()) 318 + */ 319 + mockReturnValueOnce(value: MockReturnType<T>): this 320 + /** 321 + * Accepts a value that will be resolved when the async function is called. TypeScript will only accept values that match the return type of the original function. 322 + * @example 323 + * const asyncMock = vi.fn().mockResolvedValue(42) 324 + * asyncMock() // Promise<42> 325 + */ 326 + mockResolvedValue(value: Awaited<MockReturnType<T>>): this 327 + /** 328 + * Accepts a value that will be resolved during the next function call. TypeScript will only accept values that match the return type of the original function. If chained, each consecutive call will resolve the specified value. 329 + * @example 330 + * const myMockFn = vi 331 + * .fn() 332 + * .mockResolvedValue('default') 333 + * .mockResolvedValueOnce('first call') 334 + * .mockResolvedValueOnce('second call') 335 + * 336 + * // Promise<'first call'>, Promise<'second call'>, Promise<'default'> 337 + * console.log(myMockFn(), myMockFn(), myMockFn()) 338 + */ 339 + mockResolvedValueOnce(value: Awaited<MockReturnType<T>>): this 340 + /** 341 + * Accepts an error that will be rejected when async function is called. 342 + * @example 343 + * const asyncMock = vi.fn().mockRejectedValue(new Error('Async error')) 344 + * await asyncMock() // throws Error<'Async error'> 345 + */ 346 + mockRejectedValue(error: unknown): this 347 + /** 348 + * Accepts a value that will be rejected during the next function call. If chained, each consecutive call will reject the specified value. 349 + * @example 350 + * const asyncMock = vi 351 + * .fn() 352 + * .mockResolvedValueOnce('first call') 353 + * .mockRejectedValueOnce(new Error('Async error')) 354 + * 355 + * await asyncMock() // first call 356 + * await asyncMock() // throws Error<'Async error'> 357 + */ 358 + mockRejectedValueOnce(error: unknown): this 359 + } 360 + /* eslint-enable ts/method-signature-style */ 361 + 362 + export interface Mock<T extends Procedure | Constructable = Procedure> extends MockInstance<T> { 363 + new (...args: MockParameters<T>): T extends Constructable ? InstanceType<T> : MockReturnType<T> 364 + (...args: MockParameters<T>): MockReturnType<T> 365 + /** @internal */ 366 + _isMockFunction: true 367 + } 368 + 369 + type PartialMaybePromise<T> = T extends Promise<Awaited<T>> 370 + ? Promise<Partial<Awaited<T>>> 371 + : Partial<T> 372 + 373 + export interface PartialMock<T extends Procedure = Procedure> 374 + extends MockInstance< 375 + (...args: Parameters<T>) => PartialMaybePromise<ReturnType<T>> 376 + > { 377 + new (...args: Parameters<T>): ReturnType<T> 378 + (...args: Parameters<T>): ReturnType<T> 379 + } 380 + 381 + export type MaybeMockedConstructor<T> = T extends new ( 382 + ...args: Array<any> 383 + ) => infer R 384 + ? Mock<(...args: ConstructorParameters<T>) => R> 385 + : T 386 + export type MockedFunction<T extends Procedure> = Mock<T> & { 387 + [K in keyof T]: T[K]; 388 + } 389 + export type PartiallyMockedFunction<T extends Procedure> = PartialMock<T> & { 390 + [K in keyof T]: T[K]; 391 + } 392 + export type MockedFunctionDeep<T extends Procedure> = Mock<T> 393 + & MockedObjectDeep<T> 394 + export type PartiallyMockedFunctionDeep<T extends Procedure> = PartialMock<T> 395 + & MockedObjectDeep<T> 396 + export type MockedObject<T> = MaybeMockedConstructor<T> & { 397 + [K in Methods<T>]: T[K] extends Procedure ? MockedFunction<T[K]> : T[K]; 398 + } & { [K in Properties<T>]: T[K] } 399 + export type MockedObjectDeep<T> = MaybeMockedConstructor<T> & { 400 + [K in Methods<T>]: T[K] extends Procedure ? MockedFunctionDeep<T[K]> : T[K]; 401 + } & { [K in Properties<T>]: MaybeMockedDeep<T[K]> } 402 + 403 + export type MaybeMockedDeep<T> = T extends Procedure 404 + ? MockedFunctionDeep<T> 405 + : T extends object 406 + ? MockedObjectDeep<T> 407 + : T 408 + 409 + export type MaybePartiallyMockedDeep<T> = T extends Procedure 410 + ? PartiallyMockedFunctionDeep<T> 411 + : T extends object 412 + ? MockedObjectDeep<T> 413 + : T 414 + 415 + export type MaybeMocked<T> = T extends Procedure 416 + ? MockedFunction<T> 417 + : T extends object 418 + ? MockedObject<T> 419 + : T 420 + 421 + export type MaybePartiallyMocked<T> = T extends Procedure 422 + ? PartiallyMockedFunction<T> 423 + : T extends object 424 + ? MockedObject<T> 425 + : T 426 + 427 + export interface Constructable { 428 + new (...args: any[]): any 429 + } 430 + 431 + export type MockedClass<T extends Constructable> = MockInstance< 432 + (...args: ConstructorParameters<T>) => InstanceType<T> 433 + > & { 434 + prototype: T extends { prototype: any } ? Mocked<T['prototype']> : never 435 + } & T 436 + 437 + export type Mocked<T> = { 438 + [P in keyof T]: T[P] extends Procedure 439 + ? MockInstance<T[P]> 440 + : T[P] extends Constructable 441 + ? MockedClass<T[P]> 442 + : T[P]; 443 + } & T 444 + 445 + export interface MockConfig { 446 + mockImplementation: Procedure | Constructable | undefined 447 + mockOriginal: Procedure | Constructable | undefined 448 + mockName: string 449 + onceMockImplementations: Array<Procedure | Constructable> 450 + } 451 + 452 + export interface MockInstanceOption { 453 + originalImplementation?: Procedure | Constructable 454 + mockImplementation?: Procedure | Constructable 455 + resetToMockImplementation?: boolean 456 + restore?: () => void 457 + prototypeMembers?: (string | symbol)[] 458 + keepMembersImplementation?: boolean 459 + prototypeState?: MockContext 460 + prototypeConfig?: MockConfig 461 + resetToMockName?: boolean 462 + name?: string | symbol 463 + }
+1 -1
test/browser/specs/update-snapshot.test.ts
··· 46 46 expect(snapshotData).toContain('`1`') 47 47 48 48 const testFile = readFileSync(testPath, 'utf-8') 49 - expect(testFile).toContain('expect(fn).toMatchInlineSnapshot(`[MockFunction spy]`)') 49 + expect(testFile).toContain('expect(fn).toMatchInlineSnapshot(`[MockFunction]`)') 50 50 expect(testFile).toMatchSnapshot() 51 51 52 52 // test passes
+2 -2
test/core/test/child-specific.child_process.test.ts
··· 2 2 import { expect, test } from 'vitest' 3 3 4 4 test('has access to child_process API', ({ task, skip }) => { 5 - skip(task.file.pool !== 'child_process', 'Run only in child_process pool') 5 + skip(task.file.pool !== 'forks', 'Run only in child_process pool') 6 6 expect(process.send).toBeDefined() 7 7 }) 8 8 9 9 test('doesn\'t have access to threads API', ({ task, skip }) => { 10 - skip(task.file.pool !== 'child_process', 'Run only in child_process pool') 10 + skip(task.file.pool !== 'forks', 'Run only in child_process pool') 11 11 expect(isMainThread).toBe(true) 12 12 expect(threadId).toBe(0) 13 13 })
+17 -17
test/core/test/jest-expect.test.ts
··· 714 714 describe('toHaveBeenCalled', () => { 715 715 describe('negated', () => { 716 716 it('fails if called', () => { 717 - const mock = vi.fn() 717 + const mock = vi.fn().mockName('spy') 718 718 mock() 719 719 720 720 expect(() => { ··· 753 753 describe('toHaveBeenCalledWith', () => { 754 754 describe('negated', () => { 755 755 it('fails if called', () => { 756 - const mock = vi.fn() 756 + const mock = vi.fn().mockName('spy') 757 757 mock(3) 758 758 759 759 expect(() => { ··· 766 766 describe('toHaveBeenCalledExactlyOnceWith', () => { 767 767 describe('negated', () => { 768 768 it('fails if called', () => { 769 - const mock = vi.fn() 769 + const mock = vi.fn().mockName('spy') 770 770 mock(3) 771 771 772 772 expect(() => { ··· 796 796 }) 797 797 798 798 it('fails if not called or called too many times', () => { 799 - const mock = vi.fn() 799 + const mock = vi.fn().mockName('spy') 800 800 801 801 expect(() => { 802 802 expect(mock).toHaveBeenCalledExactlyOnceWith(3) ··· 816 816 817 817 expect(() => { 818 818 expect(mock).toHaveBeenCalledExactlyOnceWith(3) 819 - }).toThrow(/^expected "spy" to be called once with arguments: \[ 3 \][^e]/) 819 + }).toThrow(/^expected "vi\.fn\(\)" to be called once with arguments: \[ 3 \][^e]/) 820 820 }) 821 821 822 822 it('passes if called exactly once with args', () => { ··· 829 829 830 830 describe('toHaveBeenCalledBefore', () => { 831 831 it('success if expect mock is called before result mock', () => { 832 - const expectMock = vi.fn() 833 - const resultMock = vi.fn() 832 + const expectMock = vi.fn().mockName('expectMock') 833 + const resultMock = vi.fn().mockName('resultMock') 834 834 835 835 expectMock() 836 836 resultMock() ··· 859 859 860 860 expect(() => { 861 861 expect(expectMock).toHaveBeenCalledBefore(resultMock) 862 - }).toThrow(/^expected "spy" to have been called before "spy"/) 862 + }).toThrow(/^expected "vi\.fn\(\)" to have been called before "vi\.fn\(\)"/) 863 863 }) 864 864 865 865 it('throws with correct mock name if failed', () => { ··· 875 875 }) 876 876 877 877 it('fails if expect mock is not called', () => { 878 - const resultMock = vi.fn() 878 + const resultMock = vi.fn().mockName('resultMock') 879 879 880 880 resultMock() 881 881 882 882 expect(() => { 883 883 expect(vi.fn()).toHaveBeenCalledBefore(resultMock) 884 - }).toThrow(/^expected "spy" to have been called before "spy"/) 884 + }).toThrow(/^expected "vi\.fn\(\)" to have been called before "resultMock"/) 885 885 }) 886 886 887 887 it('not fails if expect mock is not called with option `failIfNoFirstInvocation` set to false', () => { ··· 893 893 }) 894 894 895 895 it('fails if result mock is not called', () => { 896 - const expectMock = vi.fn() 896 + const expectMock = vi.fn().mockName('expectMock') 897 897 898 898 expectMock() 899 899 900 900 expect(() => { 901 901 expect(expectMock).toHaveBeenCalledBefore(vi.fn()) 902 - }).toThrow(/^expected "spy" to have been called before "spy"/) 902 + }).toThrow(/^expected "expectMock" to have been called before "vi\.fn\(\)"/) 903 903 }) 904 904 }) 905 905 ··· 935 935 936 936 expect(() => { 937 937 expect(expectMock).toHaveBeenCalledAfter(resultMock) 938 - }).toThrow(/^expected "spy" to have been called after "spy"/) 938 + }).toThrow(/^expected "vi\.fn\(\)" to have been called after "vi\.fn\(\)"/) 939 939 }) 940 940 941 941 it('throws with correct mock name if failed', () => { ··· 951 951 }) 952 952 953 953 it('fails if result mock is not called', () => { 954 - const expectMock = vi.fn() 954 + const expectMock = vi.fn().mockName('expectMock') 955 955 956 956 expectMock() 957 957 958 958 expect(() => { 959 959 expect(expectMock).toHaveBeenCalledAfter(vi.fn()) 960 - }).toThrow(/^expected "spy" to have been called after "spy"/) 960 + }).toThrow(/^expected "expectMock" to have been called after "vi\.fn\(\)"/) 961 961 }) 962 962 963 963 it('not fails if result mock is not called with option `failIfNoFirstInvocation` set to false', () => { ··· 969 969 }) 970 970 971 971 it('fails if expect mock is not called', () => { 972 - const resultMock = vi.fn() 972 + const resultMock = vi.fn().mockName('resultMock') 973 973 974 974 resultMock() 975 975 976 976 expect(() => { 977 977 expect(vi.fn()).toHaveBeenCalledAfter(resultMock) 978 - }).toThrow(/^expected "spy" to have been called after "spy"/) 978 + }).toThrow(/^expected "vi\.fn\(\)" to have been called after "resultMock"/) 979 979 }) 980 980 }) 981 981
+5 -52
test/core/test/jest-mock.test.ts
··· 1 1 import { describe, expect, expectTypeOf, it, vi } from 'vitest' 2 - import { rolldownVersion } from 'vitest/node' 3 2 4 3 describe('jest mock compat layer', () => { 5 4 const returnFactory = (type: string) => (value: any) => ({ type, value }) ··· 86 85 expectTypeOf(spy.mock.contexts[0]).toEqualTypeOf<SpyClass>() 87 86 expect(spy.mock.instances).toEqual([instance]) 88 87 expect(spy.mock.contexts).toEqual([instance]) 89 - }) 90 - 91 - it('throws an error when constructing a class with an arrow function', () => { 92 - function getTypeError() { 93 - // esbuild transforms it into () => {\n}, but rolldown keeps it 94 - return new TypeError(rolldownVersion 95 - ? '() => {} is not a constructor' 96 - : `() => { 97 - } is not a constructor`) 98 - } 99 - 100 - const arrow = () => {} 101 - const Fn = vi.fn(arrow) 102 - expect(() => new Fn()).toThrow(new TypeError( 103 - `The spy implementation did not use 'function' or 'class', see https://vitest.dev/api/vi#vi-spyon for examples.`, 104 - { 105 - cause: getTypeError(), 106 - }, 107 - )) 108 - 109 - const obj = { 110 - Spy: arrow, 111 - } 112 - 113 - vi.spyOn(obj, 'Spy') 114 - 115 - expect( 116 - // @ts-expect-error typescript knows you can't do that 117 - () => new obj.Spy(), 118 - ).toThrow(new TypeError( 119 - `The spy implementation did not use 'function' or 'class', see https://vitest.dev/api/vi#vi-spyon for examples.`, 120 - { 121 - cause: getTypeError(), 122 - }, 123 - )) 124 - 125 - const properClass = { 126 - Spy: class {}, 127 - } 128 - 129 - vi.spyOn(properClass, 'Spy').mockImplementation(() => {}) 130 - 131 - expect(() => new properClass.Spy()).toThrow(new TypeError( 132 - `The spy implementation did not use 'function' or 'class', see https://vitest.dev/api/vi#vi-spyon for examples.`, 133 - { 134 - cause: getTypeError(), 135 - }, 136 - )) 137 88 }) 138 89 139 90 it('implementation is set correctly on init', () => { ··· 224 175 225 176 spy.mockRestore() 226 177 227 - expect(spy.getMockImplementation()).toBe(undefined) 178 + // Sicne Vitest 4 this is a special case where 179 + // vi.fn(impl) will always return impl, unless overriden 180 + expect(spy.getMockImplementation()).toBe(originalFn) 228 181 229 182 expect(spy.mock.results).toEqual([]) 230 183 }) ··· 422 375 expect(obj.method).toHaveBeenCalledTimes(1) 423 376 vi.spyOn(obj, 'method') 424 377 obj.method() 425 - expect(obj.method).toHaveBeenCalledTimes(1) 378 + expect(obj.method).toHaveBeenCalledTimes(2) 426 379 }) 427 380 428 381 it('spyOn on the getter multiple times', () => { ··· 450 403 451 404 expect(vi.isMockFunction(obj.method)).toBe(true) 452 405 expect(obj.method()).toBe('mocked') 453 - expect(spy1).not.toBe(spy2) 406 + expect(spy1).toBe(spy2) 454 407 455 408 spy2.mockImplementation(() => 'mocked2') 456 409
+12
test/core/test/mocked-class-restore-all.test.ts
··· 29 29 expect(vi.mocked(instance0.testFn).mock.calls).toMatchInlineSnapshot(` 30 30 [ 31 31 [ 32 + "a", 33 + ], 34 + [ 32 35 "b", 33 36 ], 34 37 ] 35 38 `) 36 39 expect(vi.mocked(MockedE.prototype.testFn).mock.calls).toMatchInlineSnapshot(` 37 40 [ 41 + [ 42 + "a", 43 + ], 38 44 [ 39 45 "b", 40 46 ], ··· 53 59 expect(vi.mocked(instance0.testFn).mock.calls).toMatchInlineSnapshot(` 54 60 [ 55 61 [ 62 + "a", 63 + ], 64 + [ 56 65 "b", 57 66 ], 58 67 ] ··· 67 76 expect(vi.mocked(instance2.testFn).mock.calls).toMatchInlineSnapshot(`[]`) 68 77 expect(vi.mocked(MockedE.prototype.testFn).mock.calls).toMatchInlineSnapshot(` 69 78 [ 79 + [ 80 + "a", 81 + ], 70 82 [ 71 83 "b", 72 84 ],
+2 -2
test/core/test/mocked.test.ts
··· 91 91 expect(descriptor?.get).toBeDefined() 92 92 expect(descriptor?.set).not.toBeDefined() 93 93 94 - expect(instance.getOnlyProp).toBe(42) 94 + expect(instance.getOnlyProp).toBe(undefined) 95 95 // @ts-expect-error Assign to the read-only prop to ensure it errors. 96 96 expect(() => instance.getOnlyProp = 4).toThrow() 97 97 ··· 108 108 expect(descriptor?.get).toBeDefined() 109 109 expect(descriptor?.set).toBeDefined() 110 110 111 - expect(instance.getSetProp).toBe(123) 111 + expect(instance.getSetProp).toBe(undefined) 112 112 expect(() => instance.getSetProp = 4).not.toThrow() 113 113 114 114 const getterSpy = vi.spyOn(instance, 'getSetProp', 'get').mockReturnValue(789)
+2 -2
test/core/test/snapshot.test.ts
··· 114 114 115 115 test('renders inline mock snapshot', () => { 116 116 const fn = vi.fn() 117 - expect(fn).toMatchInlineSnapshot('[MockFunction spy]') 117 + expect(fn).toMatchInlineSnapshot('[MockFunction]') 118 118 fn('hello', 'world', 2) 119 119 expect(fn).toMatchInlineSnapshot(` 120 - [MockFunction spy] { 120 + [MockFunction] { 121 121 "calls": [ 122 122 [ 123 123 "hello",
+10
test/core/test/spy.test.ts
··· 53 53 expect(obj.A.HELLO_WORLD).toBe(true) 54 54 expect((spy as any).HELLO_WORLD).toBe(true) 55 55 }) 56 + 57 + test('ignores node.js.promisify symbol', () => { 58 + const promisifySymbol = Symbol.for('nodejs.util.promisify.custom') 59 + class Example { 60 + static [promisifySymbol] = () => Promise.resolve(42) 61 + } 62 + const obj = { Example } 63 + const spy = vi.spyOn(obj, 'Example') 64 + expect((spy as any)[promisifySymbol]).toBe(undefined) 65 + }) 56 66 })
+4 -3
packages/mocker/src/browser/mocker.ts
··· 1 + import type { CreateMockInstanceProcedure } from '../automocker' 1 2 import type { MockedModule, MockedModuleType } from '../registry' 2 3 import type { ModuleMockOptions } from '../types' 3 4 import type { ModuleMockerInterceptor } from './interceptor' ··· 16 17 constructor( 17 18 private interceptor: ModuleMockerInterceptor, 18 19 private rpc: ModuleMockerRPC, 19 - private spyOn: (obj: any, method: string | symbol) => any, 20 + private createMockInstance: CreateMockInstanceProcedure, 20 21 private config: ModuleMockerConfig, 21 22 ) {} 22 23 ··· 117 118 118 119 public mockObject( 119 120 object: Record<string | symbol, any>, 120 - moduleType: MockedModuleType = 'automock', 121 + moduleType: 'automock' | 'autospy' = 'automock', 121 122 ): Record<string | symbol, any> { 122 123 return mockObject({ 123 124 globalConstructors: { ··· 127 128 Map, 128 129 RegExp, 129 130 }, 130 - spyOn: this.spyOn, 131 + createMockInstance: this.createMockInstance, 131 132 type: moduleType, 132 133 }, object) 133 134 }
+2 -2
packages/mocker/src/browser/register.ts
··· 1 1 import type { ModuleMockerCompilerHints } from './hints' 2 2 import type { ModuleMockerInterceptor } from './index' 3 - import { spyOn } from '@vitest/spy' 3 + import { createMockInstance } from '@vitest/spy' 4 4 import { createCompilerHints } from './hints' 5 5 import { ModuleMocker } from './index' 6 6 import { hot, rpc } from './utils' ··· 24 24 return rpc('vitest:mocks:invalidate', { ids }) 25 25 }, 26 26 }, 27 - spyOn, 27 + createMockInstance, 28 28 { 29 29 root: __VITEST_MOCKER_ROOT__, 30 30 },
+4 -4
packages/vitest/src/integrations/vi.ts
··· 9 9 import type { RuntimeOptions, SerializedConfig } from '../runtime/config' 10 10 import type { VitestMocker } from '../runtime/moduleRunner/moduleMocker' 11 11 import type { MockFactoryWithHelper, MockOptions } from '../types/mocker' 12 - import { fn, isMockFunction, mocks, spyOn } from '@vitest/spy' 12 + import { clearAllMocks, fn, isMockFunction, resetAllMocks, restoreAllMocks, spyOn } from '@vitest/spy' 13 13 import { assertTypes, createSimpleStackTrace } from '@vitest/utils' 14 14 import { getWorkerState, isChildProcess, resetModules, waitForImportsToResolve } from '../runtime/utils' 15 15 import { parseSingleStack } from '../utils/source-map' ··· 656 656 }, 657 657 658 658 clearAllMocks() { 659 - [...mocks].reverse().forEach(spy => spy.mockClear()) 659 + clearAllMocks() 660 660 return utils 661 661 }, 662 662 663 663 resetAllMocks() { 664 - [...mocks].reverse().forEach(spy => spy.mockReset()) 664 + resetAllMocks() 665 665 return utils 666 666 }, 667 667 668 668 restoreAllMocks() { 669 - [...mocks].reverse().forEach(spy => spy.mockRestore()) 669 + restoreAllMocks() 670 670 return utils 671 671 }, 672 672
+2 -2
test/browser/specs/__snapshots__/update-snapshot.test.ts.snap
··· 18 18 19 19 test('renders inline mock snapshot', () => { 20 20 const fn = vi.fn() 21 - expect(fn).toMatchInlineSnapshot(\`[MockFunction spy]\`) 21 + expect(fn).toMatchInlineSnapshot(\`[MockFunction]\`) 22 22 fn('hello', 'world', 2) 23 23 expect(fn).toMatchInlineSnapshot(\` 24 - [MockFunction spy] { 24 + [MockFunction] { 25 25 "calls": [ 26 26 [ 27 27 "hello",
+2 -2
test/core/test/__snapshots__/jest-expect.test.ts.snap
··· 813 813 "expected": "Array [ 814 814 "hey", 815 815 ]", 816 - "message": "expected 2nd "spy" call to have been called with [ 'hey' ]", 816 + "message": "expected 2nd "vi.fn()" call to have been called with [ 'hey' ]", 817 817 } 818 818 `; 819 819 ··· 824 824 "expected": "Array [ 825 825 "hey", 826 826 ]", 827 - "message": "expected 3rd "spy" call to have been called with [ 'hey' ], but called only 2 times", 827 + "message": "expected 3rd "vi.fn()" call to have been called with [ 'hey' ], but called only 2 times", 828 828 } 829 829 `; 830 830
+11 -11
test/core/test/__snapshots__/mocked.test.ts.snap
··· 1 1 // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 2 3 3 exports[`mocked function which fails on toReturnWith > just one call 1`] = ` 4 - "expected "spy" to return with: 2 at least once 4 + "expected "vi.fn()" to return with: 2 at least once 5 5 6 6 Received: 7 7 8 - 1st spy call return: 8 + 1st vi.fn() call return: 9 9 10 10 - 2 11 11 + 1 ··· 16 16 `; 17 17 18 18 exports[`mocked function which fails on toReturnWith > multi calls 1`] = ` 19 - "expected "spy" to return with: 2 at least once 19 + "expected "vi.fn()" to return with: 2 at least once 20 20 21 21 Received: 22 22 23 - 1st spy call return: 23 + 1st vi.fn() call return: 24 24 25 25 - 2 26 26 + 1 27 27 28 - 2nd spy call return: 28 + 2nd vi.fn() call return: 29 29 30 30 - 2 31 31 + 1 32 32 33 - 3rd spy call return: 33 + 3rd vi.fn() call return: 34 34 35 35 - 2 36 36 + 1 ··· 41 41 `; 42 42 43 43 exports[`mocked function which fails on toReturnWith > oject type 1`] = ` 44 - "expected "spy" to return with: { a: '4' } at least once 44 + "expected "vi.fn()" to return with: { a: '4' } at least once 45 45 46 46 Received: 47 47 48 - 1st spy call return: 48 + 1st vi.fn() call return: 49 49 50 50 { 51 51 - "a": "4", 52 52 + "a": "1", 53 53 } 54 54 55 - 2nd spy call return: 55 + 2nd vi.fn() call return: 56 56 57 57 { 58 58 - "a": "4", 59 59 + "a": "1", 60 60 } 61 61 62 - 3rd spy call return: 62 + 3rd vi.fn() call return: 63 63 64 64 { 65 65 - "a": "4", ··· 72 72 `; 73 73 74 74 exports[`mocked function which fails on toReturnWith > zero call 1`] = ` 75 - "expected "spy" to return with: 2 at least once 75 + "expected "vi.fn()" to return with: 2 at least once 76 76 77 77 Number of calls: 0 78 78 "
+2 -2
test/core/test/__snapshots__/snapshot.test.ts.snap
··· 37 37 } 38 38 `; 39 39 40 - exports[`renders mock snapshot 1`] = `[MockFunction spy]`; 40 + exports[`renders mock snapshot 1`] = `[MockFunction]`; 41 41 42 42 exports[`renders mock snapshot 2`] = ` 43 - [MockFunction spy] { 43 + [MockFunction] { 44 44 "calls": [ 45 45 [ 46 46 "hello",
+2 -1
test/core/test/mocking/autospying.test.ts
··· 12 12 expect(token).toBe('123') 13 13 expect(getAuthToken).toHaveBeenCalledTimes(1) 14 14 vi.mocked(getAuthToken).mockRestore() 15 - expect(vi.isMockFunction(getAuthToken)).toBe(false) 15 + // module mocks cannot be restored 16 + expect(vi.isMockFunction(getAuthToken)).toBe(true) 16 17 }) 17 18 18 19 test('package in __mocks__ has lower priority', async () => {
+649
test/core/test/mocking/vi-fn.test.ts
··· 1 + import type { MockContext } from 'vitest' 2 + import { describe, expect, test, vi } from 'vitest' 3 + 4 + test('vi.fn() returns undefined by default', () => { 5 + const mock = vi.fn() 6 + expect(mock()).toBe(undefined) 7 + }) 8 + 9 + test('vi.fn() calls implementation if it was passed down', () => { 10 + const mock = vi.fn(() => 3) 11 + expect(mock()).toBe(3) 12 + }) 13 + 14 + test('vi.fn().mock cannot be overriden', () => { 15 + const mock = vi.fn() 16 + expect(() => mock.mock = {} as any).toThrowError() 17 + expect(() => { 18 + // @ts-expect-error mock is not optional 19 + delete mock.mock 20 + }).toThrowError() 21 + }) 22 + 23 + describe('vi.fn() state', () => { 24 + // TODO: test when calls is not empty 25 + test('vi.fn() clears calls without a custom implementation', () => { 26 + const mock = vi.fn() 27 + const state = mock.mock 28 + 29 + assertStateEmpty(state) 30 + 31 + mock() 32 + 33 + expect(state.calls).toEqual([[]]) 34 + expect(state.results).toEqual([{ type: 'return', value: undefined }]) 35 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: undefined }]) 36 + expect(state.contexts).toEqual([undefined]) 37 + expect(state.instances).toEqual([undefined]) 38 + expect(state.lastCall).toEqual([]) 39 + 40 + mock.mockClear() 41 + 42 + assertStateEmpty(state) 43 + 44 + mock() 45 + 46 + expect(state.calls).toEqual([[]]) 47 + expect(state.results).toEqual([{ type: 'return', value: undefined }]) 48 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: undefined }]) 49 + expect(state.contexts).toEqual([undefined]) 50 + expect(state.instances).toEqual([undefined]) 51 + expect(state.lastCall).toEqual([]) 52 + 53 + vi.clearAllMocks() 54 + 55 + assertStateEmpty(state) 56 + }) 57 + 58 + test('vi.fn() clears calls with a custom sync function implementation', () => { 59 + const mock = vi.fn(() => 42) 60 + const state = mock.mock 61 + 62 + assertStateEmpty(state) 63 + 64 + mock() 65 + 66 + expect(state.calls).toEqual([[]]) 67 + expect(state.results).toEqual([{ type: 'return', value: 42 }]) 68 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 42 }]) 69 + expect(state.contexts).toEqual([undefined]) 70 + expect(state.instances).toEqual([undefined]) 71 + expect(state.lastCall).toEqual([]) 72 + 73 + mock.mockClear() 74 + 75 + assertStateEmpty(state) 76 + 77 + mock() 78 + 79 + expect(state.calls).toEqual([[]]) 80 + expect(state.results).toEqual([{ type: 'return', value: 42 }]) 81 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 42 }]) 82 + expect(state.contexts).toEqual([undefined]) 83 + expect(state.instances).toEqual([undefined]) 84 + expect(state.lastCall).toEqual([]) 85 + 86 + vi.clearAllMocks() 87 + 88 + assertStateEmpty(state) 89 + }) 90 + 91 + test('vi.fn() clears calls with a custom sync function implementation with context', () => { 92 + const mock = vi.fn(() => 42) 93 + const state = mock.mock 94 + 95 + assertStateEmpty(state) 96 + 97 + mock.call('context') 98 + 99 + expect(state.calls).toEqual([[]]) 100 + expect(state.results).toEqual([{ type: 'return', value: 42 }]) 101 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 42 }]) 102 + expect(state.contexts).toEqual(['context']) 103 + expect(state.instances).toEqual(['context']) 104 + expect(state.lastCall).toEqual([]) 105 + 106 + mock.mockClear() 107 + 108 + assertStateEmpty(state) 109 + 110 + mock.call('context') 111 + 112 + expect(state.calls).toEqual([[]]) 113 + expect(state.results).toEqual([{ type: 'return', value: 42 }]) 114 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 42 }]) 115 + expect(state.contexts).toEqual(['context']) 116 + expect(state.instances).toEqual(['context']) 117 + expect(state.lastCall).toEqual([]) 118 + 119 + vi.clearAllMocks() 120 + 121 + assertStateEmpty(state) 122 + }) 123 + 124 + test('vi.fn() clears calls with a custom sync prototype function implementation', () => { 125 + const mock = vi.fn(function (this: any) { 126 + this.value = 42 127 + return 'return-string' 128 + }) 129 + const state = mock.mock 130 + 131 + assertStateEmpty(state) 132 + 133 + mock.call({}) 134 + 135 + expect(state.calls).toEqual([[]]) 136 + expect(state.results).toEqual([{ type: 'return', value: 'return-string' }]) 137 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 'return-string' }]) 138 + expect(state.contexts).toEqual([{ value: 42 }]) 139 + expect(state.instances).toEqual([{ value: 42 }]) 140 + expect(state.lastCall).toEqual([]) 141 + 142 + mock.mockClear() 143 + 144 + assertStateEmpty(state) 145 + 146 + mock.call({}) 147 + 148 + expect(state.calls).toEqual([[]]) 149 + expect(state.results).toEqual([{ type: 'return', value: 'return-string' }]) 150 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 'return-string' }]) 151 + expect(state.contexts).toEqual([{ value: 42 }]) 152 + expect(state.instances).toEqual([{ value: 42 }]) 153 + expect(state.lastCall).toEqual([]) 154 + 155 + vi.clearAllMocks() 156 + 157 + assertStateEmpty(state) 158 + }) 159 + 160 + test('vi.fn() clears calls with a custom sync class implementation', () => { 161 + const Mock = vi.fn(class { 162 + public value: number 163 + constructor() { 164 + this.value = 42 165 + } 166 + }) 167 + const state = Mock.mock 168 + 169 + assertStateEmpty(state) 170 + 171 + const mock1 = new Mock() 172 + 173 + expect(state.calls).toEqual([[]]) 174 + expect(state.results).toEqual([{ type: 'return', value: expect.any(Mock) }]) 175 + expect(state.results).toEqual([{ type: 'return', value: { value: 42 } }]) 176 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: { value: 42 } }]) 177 + expect(state.contexts).toEqual([mock1]) 178 + expect(state.instances).toEqual([mock1]) 179 + expect(state.lastCall).toEqual([]) 180 + 181 + Mock.mockClear() 182 + 183 + assertStateEmpty(state) 184 + 185 + const mock2 = new Mock() 186 + 187 + expect(state.calls).toEqual([[]]) 188 + expect(state.results).toEqual([{ type: 'return', value: mock2 }]) 189 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: mock2 }]) 190 + expect(state.contexts).toEqual([mock2]) 191 + expect(state.instances).toEqual([mock2]) 192 + expect(state.lastCall).toEqual([]) 193 + 194 + vi.clearAllMocks() 195 + 196 + assertStateEmpty(state) 197 + }) 198 + 199 + test('vi.fn() clears calls with a custom async function implementation', async () => { 200 + const mock = vi.fn(() => Promise.resolve(42)) 201 + const state = mock.mock 202 + 203 + assertStateEmpty(state) 204 + 205 + const promise = mock() 206 + 207 + expect(state.calls).toEqual([[]]) 208 + expect(state.settledResults).toEqual([{ type: 'incomplete', value: undefined }]) 209 + expect(state.results).toEqual([{ type: 'return', value: expect.any(Promise) }]) 210 + expect(state.contexts).toEqual([undefined]) 211 + expect(state.instances).toEqual([undefined]) 212 + expect(state.lastCall).toEqual([]) 213 + 214 + await promise 215 + 216 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 42 }]) 217 + 218 + mock.mockClear() 219 + 220 + assertStateEmpty(state) 221 + 222 + const promise2 = mock() 223 + 224 + expect(state.calls).toEqual([[]]) 225 + expect(state.settledResults).toEqual([{ type: 'incomplete', value: undefined }]) 226 + expect(state.results).toEqual([{ type: 'return', value: expect.any(Promise) }]) 227 + expect(state.contexts).toEqual([undefined]) 228 + expect(state.instances).toEqual([undefined]) 229 + expect(state.lastCall).toEqual([]) 230 + 231 + await promise2 232 + 233 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 42 }]) 234 + 235 + vi.clearAllMocks() 236 + 237 + assertStateEmpty(state) 238 + }) 239 + }) 240 + 241 + describe('vi.fn() configuration', () => { 242 + test('vi.fn() resets the original mock implementation', () => { 243 + const mock = vi.fn(() => 42) 244 + expect(mock()).toBe(42) 245 + mock.mockReset() 246 + expect(mock()).toBe(42) 247 + }) 248 + 249 + test('vi.fn() resets the mock implementation', () => { 250 + const mock = vi.fn().mockImplementation(() => 42) 251 + expect(mock()).toBe(42) 252 + mock.mockReset() 253 + expect(mock()).toBe(undefined) 254 + }) 255 + 256 + test('vi.fn() returns undefined as a mock implementation', () => { 257 + const mock = vi.fn() 258 + expect(mock.getMockImplementation()).toBe(undefined) 259 + }) 260 + 261 + test('vi.fn() returns implementation if it was set', () => { 262 + const implementation = () => 42 263 + const mock = vi.fn(implementation) 264 + expect(mock.getMockImplementation()).toBe(implementation) 265 + }) 266 + 267 + test('vi.fn() returns mockImplementation if it was set', () => { 268 + const implementation = () => 42 269 + const mock = vi.fn().mockImplementation(implementation) 270 + expect(mock.getMockImplementation()).toBe(implementation) 271 + }) 272 + 273 + test('vi.fn() returns mockOnceImplementation if it was set', () => { 274 + const implementation = () => 42 275 + const mock = vi.fn().mockImplementationOnce(implementation) 276 + expect(mock.getMockImplementation()).toBe(implementation) 277 + }) 278 + 279 + test('vi.fn() returns withImplementation if it was set', () => { 280 + const implementation = () => 42 281 + const mock = vi.fn() 282 + mock.withImplementation(implementation, () => { 283 + expect(mock.getMockImplementation()).toBe(implementation) 284 + }) 285 + }) 286 + 287 + test('vi.fn() has a name', () => { 288 + const mock = vi.fn() 289 + expect(mock.getMockName()).toBe('vi.fn()') 290 + mock.mockName('test') 291 + expect(mock.getMockName()).toBe('test') 292 + mock.mockReset() 293 + expect(mock.getMockName()).toBe('vi.fn()') 294 + mock.mockName('test') 295 + expect(mock.getMockName()).toBe('test') 296 + vi.resetAllMocks() 297 + expect(mock.getMockName()).toBe('vi.fn()') 298 + }) 299 + 300 + test('vi.fn() can reassign different implementations', () => { 301 + const mock = vi.fn(() => 42) 302 + expect(mock()).toBe(42) 303 + mock.mockReturnValueOnce(100) 304 + .mockReturnValueOnce(55) 305 + expect(mock()).toBe(100) 306 + expect(mock()).toBe(55) 307 + expect(mock()).toBe(42) 308 + mock.mockReturnValue(66) 309 + expect(mock()).toBe(66) 310 + }) 311 + }) 312 + 313 + describe('vi.fn() restoration', () => { 314 + test('vi.fn() resets the original implementation in mock.mockRestore()', () => { 315 + const mock = vi.fn(() => 'hello') 316 + expect(mock()).toBe('hello') 317 + mock.mockRestore() 318 + expect(mock()).toBe('hello') 319 + }) 320 + 321 + test('vi.fn() doesn\'t resets the added implementation in mock.mockRestore()', () => { 322 + const mock = vi.fn().mockImplementation(() => 'hello') 323 + expect(mock()).toBe('hello') 324 + mock.mockRestore() 325 + expect(mock()).toBe(undefined) 326 + }) 327 + 328 + test('vi.fn() doesn\'t restore the original implementation in vi.restoreAllMocks()', () => { 329 + const mock = vi.fn(() => 'hello') 330 + expect(mock()).toBe('hello') 331 + vi.restoreAllMocks() 332 + expect(mock()).toBe('hello') 333 + }) 334 + 335 + test('vi.fn() doesn\'t restore the added implementation in vi.restoreAllMocks()', () => { 336 + const mock = vi.fn().mockImplementation(() => 'hello') 337 + expect(mock()).toBe('hello') 338 + vi.restoreAllMocks() 339 + expect(mock()).toBe('hello') 340 + }) 341 + }) 342 + 343 + describe('vi.fn() implementations', () => { 344 + test('vi.fn() can throw an error in original implementation', () => { 345 + const mock = vi.fn(() => { 346 + throw new Error('hello world') 347 + }) 348 + 349 + expect(() => mock()).toThrowError('hello world') 350 + expect(mock.mock.results).toEqual([ 351 + { type: 'throw', value: new Error('hello world') }, 352 + ]) 353 + }) 354 + 355 + test('vi.fn() can throw an error in custom implementation', () => { 356 + const mock = vi.fn().mockImplementation(() => { 357 + throw new Error('hello world') 358 + }) 359 + 360 + expect(() => mock()).toThrowError('hello world') 361 + expect(mock.mock.results).toEqual([ 362 + { type: 'throw', value: new Error('hello world') }, 363 + ]) 364 + }) 365 + 366 + test('vi.fn() with mockReturnThis on a function', () => { 367 + const context = {} 368 + const mock = vi.fn() 369 + mock.mockReturnThis() 370 + expect(mock.call(context)).toBe(context) 371 + }) 372 + 373 + test('vi.fn() with mockReturnThis on a class', () => { 374 + const Mock = vi.fn(class {}) 375 + Mock.mockReturnThis() 376 + const mock = new Mock() 377 + expect(mock, 'has no effect on return value').toBeInstanceOf(Mock) 378 + expect(Mock.mock.contexts).toEqual([mock]) 379 + expect(Mock.mock.instances).toEqual([mock]) 380 + }) 381 + 382 + test('vi.fn() with mockReturnValue', () => { 383 + const mock = vi.fn() 384 + mock.mockReturnValue(42) 385 + expect(mock()).toBe(42) 386 + expect(mock()).toBe(42) 387 + expect(mock()).toBe(42) 388 + mock.mockReset() 389 + expect(mock()).toBe(undefined) 390 + }) 391 + 392 + test('vi.fn() with mockReturnValue overriding original mock', () => { 393 + const mock = vi.fn(() => 42) 394 + mock.mockReturnValue(100) 395 + expect(mock()).toBe(100) 396 + expect(mock()).toBe(100) 397 + expect(mock()).toBe(100) 398 + mock.mockReset() 399 + expect(mock()).toBe(42) 400 + }) 401 + 402 + test('vi.fn() with mockReturnValue overriding another mock', () => { 403 + const mock = vi.fn().mockImplementation(() => 42) 404 + mock.mockReturnValue(100) 405 + expect(mock()).toBe(100) 406 + expect(mock()).toBe(100) 407 + expect(mock()).toBe(100) 408 + mock.mockReset() 409 + expect(mock()).toBe(undefined) 410 + }) 411 + 412 + test('vi.fn() with mockReturnValueOnce', () => { 413 + const mock = vi.fn() 414 + mock.mockReturnValueOnce(42) 415 + expect(mock()).toBe(42) 416 + expect(mock()).toBe(undefined) 417 + expect(mock()).toBe(undefined) 418 + mock.mockReturnValueOnce(42) 419 + mock.mockReset() 420 + expect(mock()).toBe(undefined) 421 + }) 422 + 423 + test('vi.fn() with mockReturnValueOnce overriding original mock', () => { 424 + const mock = vi.fn(() => 42) 425 + mock.mockReturnValueOnce(100) 426 + expect(mock()).toBe(100) 427 + expect(mock()).toBe(42) 428 + expect(mock()).toBe(42) 429 + mock.mockReset() 430 + expect(mock()).toBe(42) 431 + }) 432 + 433 + test('vi.fn() with mockReturnValueOnce overriding another mock', () => { 434 + const mock = vi.fn().mockImplementation(() => 42) 435 + mock.mockReturnValueOnce(100) 436 + expect(mock()).toBe(100) 437 + expect(mock()).toBe(42) 438 + expect(mock()).toBe(42) 439 + mock.mockReset() 440 + expect(mock()).toBe(undefined) 441 + }) 442 + 443 + test('vi.fn() with mockResolvedValue', async () => { 444 + const mock = vi.fn() 445 + mock.mockResolvedValue(42) 446 + await expect(mock()).resolves.toBe(42) 447 + await expect(mock()).resolves.toBe(42) 448 + await expect(mock()).resolves.toBe(42) 449 + expect(mock.mock.settledResults).toEqual([ 450 + { type: 'fulfilled', value: 42 }, 451 + { type: 'fulfilled', value: 42 }, 452 + { type: 'fulfilled', value: 42 }, 453 + ]) 454 + mock.mockReset() 455 + expect(mock()).toBe(undefined) 456 + }) 457 + 458 + test('vi.fn() with mockResolvedValue overriding original mock', async () => { 459 + const mock = vi.fn(() => Promise.resolve(42)) 460 + mock.mockResolvedValue(100) 461 + await expect(mock()).resolves.toBe(100) 462 + await expect(mock()).resolves.toBe(100) 463 + await expect(mock()).resolves.toBe(100) 464 + expect(mock.mock.settledResults).toEqual([ 465 + { type: 'fulfilled', value: 100 }, 466 + { type: 'fulfilled', value: 100 }, 467 + { type: 'fulfilled', value: 100 }, 468 + ]) 469 + mock.mockReset() 470 + await expect(mock()).resolves.toBe(42) 471 + }) 472 + 473 + test('vi.fn() with mockResolvedValue overriding another mock', async () => { 474 + const mock = vi.fn().mockImplementation(() => 42) 475 + mock.mockResolvedValue(100) 476 + await expect(mock()).resolves.toBe(100) 477 + await expect(mock()).resolves.toBe(100) 478 + await expect(mock()).resolves.toBe(100) 479 + expect(mock.mock.settledResults).toEqual([ 480 + { type: 'fulfilled', value: 100 }, 481 + { type: 'fulfilled', value: 100 }, 482 + { type: 'fulfilled', value: 100 }, 483 + ]) 484 + mock.mockReset() 485 + expect(mock()).toBe(undefined) 486 + }) 487 + 488 + test('vi.fn() with mockResolvedValueOnce', async () => { 489 + const mock = vi.fn() 490 + mock.mockResolvedValueOnce(42) 491 + await expect(mock()).resolves.toBe(42) 492 + expect(mock()).toBe(undefined) 493 + expect(mock()).toBe(undefined) 494 + mock.mockResolvedValueOnce(42) 495 + mock.mockReset() 496 + expect(mock()).toBe(undefined) 497 + }) 498 + 499 + test('vi.fn() with mockResolvedValueOnce overriding original mock', async () => { 500 + const mock = vi.fn(() => Promise.resolve(42)) 501 + mock.mockResolvedValueOnce(100) 502 + await expect(mock()).resolves.toBe(100) 503 + await expect(mock()).resolves.toBe(42) 504 + await expect(mock()).resolves.toBe(42) 505 + mock.mockReset() 506 + await expect(mock()).resolves.toBe(42) 507 + }) 508 + 509 + test('vi.fn() with mockResolvedValueOnce overriding another mock', async () => { 510 + const mock = vi.fn().mockImplementation(() => Promise.resolve(42)) 511 + mock.mockResolvedValueOnce(100) 512 + await expect(mock()).resolves.toBe(100) 513 + await expect(mock()).resolves.toBe(42) 514 + await expect(mock()).resolves.toBe(42) 515 + mock.mockReset() 516 + expect(mock()).toBe(undefined) 517 + }) 518 + 519 + test('vi.fn() with mockRejectedValue', async () => { 520 + const mock = vi.fn() 521 + mock.mockRejectedValue(42) 522 + await expect(mock()).rejects.toBe(42) 523 + await expect(mock()).rejects.toBe(42) 524 + await expect(mock()).rejects.toBe(42) 525 + expect(mock.mock.settledResults).toEqual([ 526 + { type: 'rejected', value: 42 }, 527 + { type: 'rejected', value: 42 }, 528 + { type: 'rejected', value: 42 }, 529 + ]) 530 + mock.mockReset() 531 + expect(mock()).toBe(undefined) 532 + }) 533 + 534 + test('vi.fn() with mockRejectedValue overriding original mock', async () => { 535 + const mock = vi.fn(() => Promise.resolve(42)) 536 + mock.mockRejectedValue(100) 537 + await expect(mock()).rejects.toBe(100) 538 + await expect(mock()).rejects.toBe(100) 539 + await expect(mock()).rejects.toBe(100) 540 + expect(mock.mock.settledResults).toEqual([ 541 + { type: 'rejected', value: 100 }, 542 + { type: 'rejected', value: 100 }, 543 + { type: 'rejected', value: 100 }, 544 + ]) 545 + mock.mockReset() 546 + await expect(mock()).resolves.toBe(42) 547 + }) 548 + 549 + test('vi.fn() with mockRejectedValue overriding another mock', async () => { 550 + const mock = vi.fn().mockImplementation(() => Promise.resolve(42)) 551 + mock.mockRejectedValue(100) 552 + await expect(mock()).rejects.toBe(100) 553 + await expect(mock()).rejects.toBe(100) 554 + await expect(mock()).rejects.toBe(100) 555 + expect(mock.mock.settledResults).toEqual([ 556 + { type: 'rejected', value: 100 }, 557 + { type: 'rejected', value: 100 }, 558 + { type: 'rejected', value: 100 }, 559 + ]) 560 + mock.mockReset() 561 + expect(mock()).toBe(undefined) 562 + }) 563 + 564 + test('vi.fn() with mockRejectedValueOnce', async () => { 565 + const mock = vi.fn() 566 + mock.mockRejectedValueOnce(42) 567 + await expect(mock()).rejects.toBe(42) 568 + expect(mock()).toBe(undefined) 569 + expect(mock()).toBe(undefined) 570 + mock.mockRejectedValueOnce(42) 571 + mock.mockReset() 572 + expect(mock()).toBe(undefined) 573 + }) 574 + 575 + test('vi.fn() with mockRejectedValueOnce overriding original mock', async () => { 576 + const mock = vi.fn(() => Promise.resolve(42)) 577 + mock.mockRejectedValueOnce(100) 578 + await expect(mock()).rejects.toBe(100) 579 + await expect(mock()).resolves.toBe(42) 580 + await expect(mock()).resolves.toBe(42) 581 + mock.mockReset() 582 + await expect(mock()).resolves.toBe(42) 583 + }) 584 + 585 + test('vi.fn() with mockRejectedValueOnce overriding another mock', async () => { 586 + const mock = vi.fn().mockImplementation(() => Promise.resolve(42)) 587 + mock.mockRejectedValueOnce(100) 588 + await expect(mock()).rejects.toBe(100) 589 + await expect(mock()).resolves.toBe(42) 590 + await expect(mock()).resolves.toBe(42) 591 + mock.mockReset() 592 + expect(mock()).toBe(undefined) 593 + }) 594 + 595 + test('vi.fn() throws an error if new is called on arrow function', ({ onTestFinished }) => { 596 + const log = vi.spyOn(console, 'warn') 597 + onTestFinished(() => log.mockRestore()) 598 + const Mock = vi.fn(() => {}) 599 + expect(() => new Mock()).toThrowError() 600 + expect(log).toHaveBeenCalledWith( 601 + `[vitest] The vi.fn() mock did not use 'function' or 'class' in its implementation, see https://vitest.dev/api/vi#vi-spyon for examples.`, 602 + ) 603 + }) 604 + 605 + test('vi.fn() throws an error if new is not called on a class', () => { 606 + const Mock = vi.fn(class _Mock {}) 607 + expect(() => Mock()).toThrowError( 608 + `Class constructor _Mock cannot be invoked without 'new'`, 609 + ) 610 + }) 611 + 612 + test('vi.fn() respects new target in a function', () => { 613 + let target!: unknown 614 + let callArgs!: unknown[] 615 + const Mock = vi.fn(function (this: any, ...args: unknown[]) { 616 + target = new.target 617 + callArgs = args 618 + }) 619 + const _example = new Mock('test', 42) 620 + expect(target).toBeTypeOf('function') 621 + expect(callArgs).toEqual(['test', 42]) 622 + expect(Mock.mock.calls).toEqual([['test', 42]]) 623 + }) 624 + 625 + test('vi.fn() respects new target in a class', () => { 626 + let target!: unknown 627 + let callArgs!: unknown[] 628 + const Mock = vi.fn(class { 629 + constructor(...args: any[]) { 630 + target = new.target 631 + callArgs = args 632 + } 633 + }) 634 + const _example = new Mock('test', 42) 635 + expect(target).toBeTypeOf('function') 636 + expect(callArgs).toEqual(['test', 42]) 637 + expect(Mock.mock.calls).toEqual([['test', 42]]) 638 + }) 639 + }) 640 + 641 + function assertStateEmpty(state: MockContext<any>) { 642 + expect(state.calls).toHaveLength(0) 643 + expect(state.results).toHaveLength(0) 644 + expect(state.settledResults).toHaveLength(0) 645 + expect(state.contexts).toHaveLength(0) 646 + expect(state.instances).toHaveLength(0) 647 + expect(state.lastCall).toBe(undefined) 648 + expect(state.invocationCallOrder).toEqual([]) 649 + }
+236
test/core/test/mocking/vi-mockObject.test.ts
··· 1 + import { expect, test, vi } from 'vitest' 2 + 3 + test('vi.mockObject() mocks methods', () => { 4 + const mocked = mockModule() 5 + 6 + expect(mocked.method.name).toBe('method') 7 + expect(mocked.Class.name).toBe('Class') 8 + expect(mocked.method()).toBe(undefined) 9 + expect(new mocked.Class()).toBeInstanceOf(mocked.Class) 10 + }) 11 + 12 + test('when properties are spied, they keep the implementation', () => { 13 + const module = mockModule('autospy') 14 + expect(module.method()).toBe(42) 15 + expect(module.method).toHaveBeenCalled() 16 + 17 + const instance = new module.Class() 18 + expect(instance.method()).toBe(42) 19 + expect(instance.method).toHaveBeenCalled() 20 + expect(module.Class.prototype.method).toHaveBeenCalledTimes(1) 21 + 22 + vi.mocked(instance.method).mockReturnValue(100) 23 + expect(instance.method()).toBe(100) 24 + expect(module.Class.prototype.method).toHaveBeenCalledTimes(2) 25 + 26 + vi.mocked(instance.method).mockReset() 27 + expect(instance.method()).toBe(42) 28 + expect(module.Class.prototype.method).toHaveBeenCalledTimes(3) 29 + }) 30 + 31 + test('vi.restoreAllMocks() does not affect mocks', () => { 32 + const mocked = mockModule() 33 + 34 + vi.restoreAllMocks() 35 + 36 + expect(mocked.method()).toBe(undefined) 37 + expect(new mocked.Class()).toBeInstanceOf(mocked.Class) 38 + }) 39 + 40 + test('vi.mockRestore() does not affect mocks', () => { 41 + const mocked = mockModule() 42 + 43 + vi.mocked(mocked.method).mockRestore() 44 + 45 + expect(mocked.method()).toBe(undefined) 46 + expect(new mocked.Class()).toBeInstanceOf(mocked.Class) 47 + }) 48 + 49 + test('vi.mockRestore() on respied method does not restore it to the original', async ({ annotate }) => { 50 + await annotate('https://github.com/vitest-dev/vitest/issues/8319', 'issue') 51 + 52 + const mocked = mockModule() 53 + const spy = vi.spyOn(mocked, 'method') 54 + 55 + expect(mocked.method()).toBe(undefined) 56 + 57 + spy.mockRestore() 58 + 59 + expect(mocked.method()).toBe(undefined) 60 + }) 61 + 62 + test('instance mocks are independently tracked, but prototype shares the state', () => { 63 + const { Class } = mockModule() 64 + const t1 = new Class() 65 + const t2 = new Class() 66 + t1.method() 67 + expect(t1.method).toHaveBeenCalledTimes(1) 68 + t2.method() 69 + expect(t1.method).toHaveBeenCalledTimes(1) 70 + expect(t2.method).toHaveBeenCalledTimes(1) 71 + expect(Class.prototype.method).toHaveBeenCalledTimes(2) 72 + 73 + vi.resetAllMocks() 74 + t1.method() 75 + expect(t1.method).toHaveBeenCalledTimes(1) 76 + t2.method() 77 + expect(t1.method).toHaveBeenCalledTimes(1) 78 + expect(t2.method).toHaveBeenCalledTimes(1) 79 + expect(Class.prototype.method).toHaveBeenCalledTimes(2) 80 + 81 + vi.mocked(t1.method).mockReturnValue(100) 82 + t1.method() 83 + expect(t1.method).toHaveBeenCalledTimes(2) 84 + // tracks methods even when t1.method implementation is overriden 85 + expect(Class.prototype.method).toHaveBeenCalledTimes(3) 86 + }) 87 + 88 + test('instance methods and prototype method share the state', () => { 89 + const { Class } = mockModule() 90 + const t1 = vi.mocked(new Class()) 91 + 92 + expect(t1.method.mock).toEqual(Class.prototype.method.mock) 93 + 94 + t1.method('hello world', Symbol.for('example')) 95 + 96 + expect(t1.method.mock.calls[0][0]).toBe('hello world') 97 + expect(t1.method.mock.calls[0][1]).toBe(Symbol.for('example')) 98 + 99 + expect(t1.method.mock.instances[0]).toBe(t1) 100 + expect(t1.method.mock.contexts[0]).toBe(t1) 101 + expect(t1.method.mock.results[0]).toEqual({ 102 + type: 'return', 103 + value: undefined, 104 + }) 105 + 106 + expect(Class.prototype.method.mock.calls[0][0]).toBe('hello world') 107 + expect(Class.prototype.method.mock.calls[0][1]).toBe(Symbol.for('example')) 108 + 109 + expect(t1.method.mock).toEqual(Class.prototype.method.mock) 110 + 111 + const t2 = vi.mocked(new Class()) 112 + 113 + t2.method('bye world') 114 + 115 + expect(t1.method).toHaveBeenCalledTimes(1) 116 + expect(t2.method).toHaveBeenCalledTimes(1) 117 + 118 + expect(t2.method.mock.calls[0][0]).toBe('bye world') 119 + // note that Class.prototype.method keeps accumulating state 120 + expect(Class.prototype.method.mock.calls[1][0]).toBe('bye world') 121 + }) 122 + 123 + test('instance methods inherit the implementation, but can override the local ones', () => { 124 + const { Class } = mockModule() 125 + const t1 = vi.mocked(new Class()) 126 + const t2 = vi.mocked(new Class()) 127 + 128 + t1.method.mockReturnValue(100) 129 + expect(t1.method()).toBe(100) 130 + expect(t1.method).toHaveBeenCalled() 131 + expect(t2.method).not.toHaveBeenCalled() 132 + 133 + expect(Class.prototype.method).toHaveBeenCalledTimes(1) 134 + 135 + Class.prototype.method.mockReturnValue(200) 136 + expect(t1.method()).toBe(100) 137 + expect(t2.method()).toBe(200) 138 + 139 + expect(Class.prototype.method).toHaveBeenCalledTimes(3) 140 + 141 + vi.resetAllMocks() 142 + 143 + expect(t1.method()).toBe(undefined) 144 + expect(t2.method()).toBe(undefined) 145 + 146 + Class.prototype.method.mockReturnValue(300) 147 + 148 + expect(t1.method()).toBe(300) 149 + expect(t2.method()).toBe(300) 150 + }) 151 + 152 + test('vi.mockReset() does not break inherited properties', () => { 153 + const { Class } = mockModule() 154 + const instance1 = new Class() 155 + 156 + expect(instance1.method()).toBe(undefined) 157 + 158 + expect(instance1.method).toHaveBeenCalledTimes(1) 159 + expect(Class.prototype.method).toHaveBeenCalledTimes(1) 160 + 161 + vi.mocked(instance1.method).mockReturnValue(100) 162 + 163 + expect(instance1.method()).toBe(100) 164 + 165 + expect(instance1.method).toHaveBeenCalledTimes(2) 166 + expect(Class.prototype.method).toHaveBeenCalledTimes(2) 167 + 168 + vi.resetAllMocks() 169 + 170 + expect(instance1.method()).toBe(undefined) 171 + 172 + expect(instance1.method).toHaveBeenCalledTimes(1) 173 + expect(Class.prototype.method).toHaveBeenCalledTimes(1) 174 + 175 + const instance2 = new Class() 176 + const instance3 = new Class() 177 + 178 + instance2.method() 179 + 180 + expect(instance2.method).not.toBe(instance3.method) 181 + 182 + expect(instance2.method).toHaveBeenCalledTimes(1) 183 + expect(instance3.method).toHaveBeenCalledTimes(0) 184 + }) 185 + 186 + test('the array is empty by default', () => { 187 + const { array } = vi.mockObject({ 188 + array: [1, 2, 3], 189 + }) 190 + expect(array).toEqual([]) 191 + }) 192 + 193 + test('the array is not empty when spying', () => { 194 + const { array } = vi.mockObject( 195 + { 196 + array: [ 197 + 1, 198 + 'text', 199 + () => 42, 200 + { 201 + property: 'static', 202 + answer() { 203 + return 42 204 + }, 205 + array: [1], 206 + }, 207 + ] as const, 208 + }, 209 + { spy: true }, 210 + ) 211 + 212 + expect(array).toHaveLength(4) 213 + expect(array[0]).toBe(1) 214 + expect(array[1]).toBe('text') 215 + expect(vi.isMockFunction(array[2])).toBe(true) 216 + expect(array[2]()).toBe(42) 217 + expect(array[3].property).toBe('static') 218 + expect(array[3].answer()).toBe(42) 219 + expect(array[3].answer).toHaveBeenCalled() 220 + expect(array[3].array).toEqual([1]) 221 + }) 222 + 223 + function mockModule(type: 'automock' | 'autospy' = 'automock') { 224 + return vi.mockObject({ 225 + [Symbol.toStringTag]: 'Module', 226 + __esModule: true, 227 + method(..._args: any[]) { 228 + return 42 229 + }, 230 + Class: class { 231 + method(..._args: any[]) { 232 + return 42 233 + } 234 + }, 235 + }, { spy: type === 'autospy' }) 236 + }
+622
test/core/test/mocking/vi-spyOn.test.ts
··· 1 + import type { MockContext } from 'vitest' 2 + import { describe, expect, test, vi } from 'vitest' 3 + 4 + describe('vi.spyOn() state', () => { 5 + test('vi.spyOn() spies on an object and tracks the calls', () => { 6 + const object = createObject() 7 + const mock = vi.spyOn(object, 'method') 8 + 9 + expect(object.method).toBe(mock) 10 + expect(vi.isMockFunction(object.method)).toBe(true) 11 + 12 + const state = mock.mock 13 + 14 + assertStateEmpty(state) 15 + 16 + object.method() 17 + expect(state.calls).toEqual([[]]) 18 + expect(state.results).toEqual([{ type: 'return', value: 42 }]) 19 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 42 }]) 20 + expect(state.instances).toEqual([object]) 21 + expect(state.contexts).toEqual([object]) 22 + expect(state.lastCall).toEqual([]) 23 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 24 + 25 + mock.mockClear() 26 + assertStateEmpty(state) 27 + 28 + object.method() 29 + expect(state.calls).toEqual([[]]) 30 + expect(state.results).toEqual([{ type: 'return', value: 42 }]) 31 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 42 }]) 32 + expect(state.instances).toEqual([object]) 33 + expect(state.contexts).toEqual([object]) 34 + expect(state.lastCall).toEqual([]) 35 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 36 + 37 + vi.clearAllMocks() 38 + assertStateEmpty(state) 39 + }) 40 + 41 + test('vi.spyOn() spies and tracks overriden sync calls', () => { 42 + const object = createObject() 43 + const mock = vi.spyOn(object, 'method') 44 + mock.mockImplementation(() => 100) 45 + const state = mock.mock 46 + 47 + assertStateEmpty(state) 48 + 49 + object.method() 50 + expect(state.calls).toEqual([[]]) 51 + expect(state.results).toEqual([{ type: 'return', value: 100 }]) 52 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 100 }]) 53 + expect(state.instances).toEqual([object]) 54 + expect(state.contexts).toEqual([object]) 55 + expect(state.lastCall).toEqual([]) 56 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 57 + 58 + mock.mockClear() 59 + assertStateEmpty(state) 60 + 61 + object.method() 62 + expect(state.calls).toEqual([[]]) 63 + expect(state.results).toEqual([{ type: 'return', value: 100 }]) 64 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 100 }]) 65 + expect(state.instances).toEqual([object]) 66 + expect(state.contexts).toEqual([object]) 67 + expect(state.lastCall).toEqual([]) 68 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 69 + 70 + vi.clearAllMocks() 71 + assertStateEmpty(state) 72 + }) 73 + 74 + test('vi.spyOn() spies and tracks overriden sync calls with context', () => { 75 + const object = createObject() 76 + const mock = vi.spyOn(object, 'method') 77 + mock.mockImplementation(() => 100) 78 + const state = mock.mock 79 + const context = {} 80 + 81 + assertStateEmpty(state) 82 + 83 + object.method.call(context) 84 + expect(state.calls).toEqual([[]]) 85 + expect(state.results).toEqual([{ type: 'return', value: 100 }]) 86 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 100 }]) 87 + expect(state.instances).toEqual([context]) 88 + expect(state.contexts).toEqual([context]) 89 + expect(state.lastCall).toEqual([]) 90 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 91 + 92 + mock.mockClear() 93 + assertStateEmpty(state) 94 + 95 + object.method.call(context) 96 + expect(state.calls).toEqual([[]]) 97 + expect(state.results).toEqual([{ type: 'return', value: 100 }]) 98 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 100 }]) 99 + expect(state.instances).toEqual([context]) 100 + expect(state.contexts).toEqual([context]) 101 + expect(state.lastCall).toEqual([]) 102 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 103 + 104 + vi.clearAllMocks() 105 + assertStateEmpty(state) 106 + }) 107 + 108 + test('vi.spyOn() spies and tracks overriden sync prototype calls with context', () => { 109 + const object = createObject() 110 + const mock = vi.spyOn(object, 'method') 111 + mock.mockImplementation(function (this: any) { 112 + this.value = 42 113 + return 100 114 + }) 115 + const state = mock.mock 116 + const context = {} 117 + 118 + assertStateEmpty(state) 119 + 120 + object.method.call(context) 121 + expect(state.calls).toEqual([[]]) 122 + expect(state.results).toEqual([{ type: 'return', value: 100 }]) 123 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 100 }]) 124 + expect(state.instances).toEqual([{ value: 42 }]) 125 + expect(state.contexts).toEqual([{ value: 42 }]) 126 + expect(state.lastCall).toEqual([]) 127 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 128 + 129 + mock.mockClear() 130 + assertStateEmpty(state) 131 + 132 + object.method.call(context) 133 + expect(state.calls).toEqual([[]]) 134 + expect(state.results).toEqual([{ type: 'return', value: 100 }]) 135 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 100 }]) 136 + expect(state.instances).toEqual([{ value: 42 }]) 137 + expect(state.contexts).toEqual([{ value: 42 }]) 138 + expect(state.lastCall).toEqual([]) 139 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 140 + 141 + vi.clearAllMocks() 142 + assertStateEmpty(state) 143 + }) 144 + 145 + test('vi.spyOn() spies and tracks overriden sync class calls with context', () => { 146 + const object = createObject() 147 + const mock = vi.spyOn(object, 'Class') 148 + mock.mockImplementation(class { 149 + public value: number 150 + constructor() { 151 + this.value = 42 152 + } 153 + }) 154 + const state = mock.mock 155 + 156 + assertStateEmpty(state) 157 + 158 + const instance1 = new object.Class() 159 + expect(state.calls).toEqual([[]]) 160 + expect(state.results).toEqual([{ type: 'return', value: instance1 }]) 161 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: instance1 }]) 162 + expect(state.instances).toEqual([instance1]) 163 + expect(state.contexts).toEqual([instance1]) 164 + expect(state.lastCall).toEqual([]) 165 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 166 + 167 + mock.mockClear() 168 + assertStateEmpty(state) 169 + 170 + const instance2 = new object.Class() 171 + expect(state.calls).toEqual([[]]) 172 + expect(state.results).toEqual([{ type: 'return', value: instance2 }]) 173 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: instance2 }]) 174 + expect(state.instances).toEqual([instance2]) 175 + expect(state.contexts).toEqual([instance2]) 176 + expect(state.lastCall).toEqual([]) 177 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 178 + 179 + vi.clearAllMocks() 180 + assertStateEmpty(state) 181 + }) 182 + 183 + test('vi.spyOn() spies and tracks overriden async calls', async () => { 184 + const object = createObject() 185 + const mock = vi.spyOn(object, 'async') 186 + mock.mockImplementation(() => Promise.resolve(100)) 187 + const state = mock.mock 188 + 189 + assertStateEmpty(state) 190 + 191 + const promise1 = object.async() 192 + expect(state.calls).toEqual([[]]) 193 + expect(state.results).toEqual([{ type: 'return', value: expect.any(Promise) }]) 194 + expect(state.settledResults).toEqual([{ type: 'incomplete', value: undefined }]) 195 + expect(state.instances).toEqual([object]) 196 + expect(state.contexts).toEqual([object]) 197 + expect(state.lastCall).toEqual([]) 198 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 199 + 200 + await promise1 201 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 100 }]) 202 + 203 + mock.mockClear() 204 + assertStateEmpty(state) 205 + 206 + const promise2 = object.async() 207 + expect(state.calls).toEqual([[]]) 208 + expect(state.results).toEqual([{ type: 'return', value: expect.any(Promise) }]) 209 + expect(state.settledResults).toEqual([{ type: 'incomplete', value: undefined }]) 210 + expect(state.instances).toEqual([object]) 211 + expect(state.contexts).toEqual([object]) 212 + expect(state.lastCall).toEqual([]) 213 + expect(state.invocationCallOrder).toEqual([expect.any(Number)]) 214 + 215 + await promise2 216 + expect(state.settledResults).toEqual([{ type: 'fulfilled', value: 100 }]) 217 + 218 + vi.clearAllMocks() 219 + assertStateEmpty(state) 220 + }) 221 + 222 + test('vi.spyOn() doesn\'t loose context', () => { 223 + const instances: any[] = [] 224 + const Names = function Names(this: any) { 225 + instances.push(this) 226 + this.array = [1] 227 + } as { 228 + (): void 229 + new (): typeof obj 230 + } 231 + const obj = { 232 + array: [], 233 + Names, 234 + } 235 + 236 + vi.spyOn(obj, 'Names') 237 + 238 + const s = new obj.Names() 239 + 240 + expect(obj.array).toEqual([]) 241 + expect(s.array).toEqual([1]) 242 + expect(instances[0]).toEqual({ array: [1] }) 243 + 244 + obj.Names() 245 + 246 + expect(obj.array).toEqual([1]) 247 + }) 248 + }) 249 + 250 + describe('vi.spyOn() settings', () => { 251 + test('vi.spyOn() when spying on a method spy returns the same spy', () => { 252 + const object = createObject() 253 + const spy1 = vi.spyOn(object, 'method') 254 + const spy2 = vi.spyOn(object, 'method') 255 + expect(spy1).toBe(spy2) 256 + 257 + object.method() 258 + expect(spy2.mock.calls).toEqual(spy2.mock.calls) 259 + }) 260 + 261 + test('vi.spyOn() when spying on a getter spy returns the same spy', () => { 262 + const object = createObject() 263 + const spy1 = vi.spyOn(object, 'getter', 'get') 264 + const spy2 = vi.spyOn(object, 'getter', 'get') 265 + expect(spy1).toBe(spy2) 266 + 267 + const _example = object.getter 268 + expect(spy2).toHaveBeenCalledTimes(1) 269 + expect(spy1).toHaveBeenCalledTimes(1) 270 + expect(spy2.mock.calls).toEqual(spy1.mock.calls) 271 + }) 272 + 273 + test('vi.spyOn() when spying on a setter spy returns the same spy', () => { 274 + const object = createObject() 275 + const spy1 = vi.spyOn(object, 'getter', 'set') 276 + const spy2 = vi.spyOn(object, 'getter', 'set') 277 + expect(spy1).toBe(spy2) 278 + 279 + object.getter = 33 280 + expect(spy2).toHaveBeenCalledTimes(1) 281 + expect(spy1).toHaveBeenCalledTimes(1) 282 + expect(spy2.mock.calls).toEqual(spy1.mock.calls) 283 + }) 284 + 285 + test('vi.spyOn() can spy on multiple class instances without intervention', () => { 286 + class Example { 287 + method() { 288 + return 42 289 + } 290 + } 291 + 292 + const example1 = new Example() 293 + const example2 = new Example() 294 + 295 + const mock1 = vi.spyOn(example1, 'method') 296 + const mock2 = vi.spyOn(example2, 'method') 297 + 298 + example1.method() 299 + expect(mock1.mock.calls).toHaveLength(1) 300 + expect(mock2.mock.calls).toHaveLength(0) 301 + 302 + example1.method() 303 + expect(mock1.mock.calls).toHaveLength(2) 304 + expect(mock2.mock.calls).toHaveLength(0) 305 + 306 + example2.method() 307 + expect(mock1.mock.calls).toHaveLength(2) 308 + expect(mock2.mock.calls).toHaveLength(1) 309 + }) 310 + 311 + test('vi.spyOn() can spy on a prototype', () => { 312 + class Example { 313 + method() { 314 + return 42 315 + } 316 + } 317 + 318 + const example = new Example() 319 + const spy = vi.spyOn(example, 'method') 320 + expect(example.method()).toBe(42) 321 + expect(spy.mock.calls).toEqual([[]]) 322 + expect(vi.isMockFunction(Example.prototype.method)).toBe(false) 323 + }) 324 + 325 + test('vi.spyOn() can spy on inherited methods', () => { 326 + class Bar { 327 + _bar = 'bar' 328 + get bar(): string { 329 + return this._bar 330 + } 331 + 332 + set bar(bar: string) { 333 + this._bar = bar 334 + } 335 + } 336 + class Foo extends Bar {} 337 + const foo = new Foo() 338 + vi.spyOn(foo, 'bar', 'get').mockImplementation(() => 'foo') 339 + expect(foo.bar).toEqual('foo') 340 + // foo.bar setter is inherited from Bar, so we can set it 341 + expect(() => { 342 + foo.bar = 'baz' 343 + }).not.toThrowError() 344 + expect(foo.bar).toEqual('foo') 345 + }) 346 + 347 + test('vi.spyOn() inherits overriden methods', () => { 348 + class Bar { 349 + _bar = 'bar' 350 + get bar(): string { 351 + return this._bar 352 + } 353 + 354 + set bar(bar: string) { 355 + this._bar = bar 356 + } 357 + } 358 + class Foo extends Bar { 359 + get bar(): string { 360 + return `${super.bar}-foo` 361 + } 362 + } 363 + const foo = new Foo() 364 + expect(foo.bar).toEqual('bar-foo') 365 + vi.spyOn(foo, 'bar', 'get').mockImplementation(() => 'foo') 366 + expect(foo.bar).toEqual('foo') 367 + // foo.bar setter is not inherited from Bar 368 + expect(() => { 369 + // @ts-expect-error bar cannot be overriden 370 + foo.bar = 'baz' 371 + }).toThrowError() 372 + expect(foo.bar).toEqual('foo') 373 + }) 374 + 375 + test('vi.spyOn().mockReset() resets the implementation', () => { 376 + const object = createObject() 377 + const spy = vi.spyOn(object, 'method').mockImplementation(() => 100) 378 + expect(object.method()).toBe(100) 379 + spy.mockReset() 380 + expect(object.method()).toBe(42) 381 + }) 382 + 383 + test('vi.spyOn() resets the implementation in resetAllMocks', () => { 384 + const object = createObject() 385 + vi.spyOn(object, 'method').mockImplementation(() => 100) 386 + expect(object.method()).toBe(100) 387 + vi.resetAllMocks() 388 + expect(object.method()).toBe(42) 389 + }) 390 + 391 + test('vi.spyOn() returns undefined as mockImplementation', () => { 392 + const object = createObject() 393 + const spy = vi.spyOn(object, 'method') 394 + expect(spy.getMockImplementation()).toBe(undefined) 395 + }) 396 + 397 + test('vi.spyOn() returns implementation if it was set', () => { 398 + const implementation = () => 42 399 + const object = createObject() 400 + const spy = vi.spyOn(object, 'method').mockImplementation(implementation) 401 + expect(spy.getMockImplementation()).toBe(implementation) 402 + spy.mockReset() 403 + expect(spy.getMockImplementation()).toBe(undefined) 404 + }) 405 + 406 + test('vi.spyOn() returns mockOnceImplementation if it was set', () => { 407 + const implementation = () => 42 408 + const object = createObject() 409 + const spy = vi.spyOn(object, 'method').mockImplementationOnce(implementation) 410 + expect(spy.getMockImplementation()).toBe(implementation) 411 + }) 412 + 413 + test('vi.spyOn() returns withImplementation if it was set', () => { 414 + const implementation = () => 42 415 + const object = createObject() 416 + const spy = vi.spyOn(object, 'method') 417 + spy.withImplementation(implementation, () => { 418 + expect(spy.getMockImplementation()).toBe(implementation) 419 + }) 420 + }) 421 + 422 + test('vi.spyOn() has a name', () => { 423 + const object = createObject() 424 + const spy = vi.spyOn(object, 'method') 425 + expect(spy.getMockName()).toBe('method') 426 + spy.mockName('test') 427 + expect(spy.getMockName()).toBe('test') 428 + spy.mockReset() 429 + expect(spy.getMockName()).toBe('method') 430 + spy.mockName('test') 431 + expect(spy.getMockName()).toBe('test') 432 + vi.resetAllMocks() 433 + expect(spy.getMockName()).toBe('method') 434 + }) 435 + }) 436 + 437 + describe('vi.spyOn() restoration', () => { 438 + test('vi.spyOn() cannot spy on undefined or null', () => { 439 + expect(() => vi.spyOn(undefined as any, 'test')).toThrowError('The vi.spyOn() function could not find an object to spy upon. The first argument must be defined.') 440 + expect(() => vi.spyOn(null as any, 'test')).toThrowError('The vi.spyOn() function could not find an object to spy upon. The first argument must be defined.') 441 + }) 442 + 443 + test('vi.spyOn() cannot spy on a primitive value', () => { 444 + expect(() => vi.spyOn('string' as any, 'toString')).toThrowError('Vitest cannot spy on a primitive value.') 445 + expect(() => vi.spyOn(0 as any, 'toString')).toThrowError('Vitest cannot spy on a primitive value.') 446 + expect(() => vi.spyOn(true as any, 'toString')).toThrowError('Vitest cannot spy on a primitive value.') 447 + expect(() => vi.spyOn(1n as any, 'toString')).toThrowError('Vitest cannot spy on a primitive value.') 448 + expect(() => vi.spyOn(Symbol.toStringTag as any, 'toString')).toThrowError('Vitest cannot spy on a primitive value.') 449 + }) 450 + 451 + test('vi.spyOn() cannot spy on non-existing property', () => { 452 + expect(() => vi.spyOn({} as any, 'never')).toThrowError('The property "never" is not defined on the object.') 453 + }) 454 + 455 + test('vi.spyOn() restores the original method when .mockRestore() is called', () => { 456 + const object = createObject() 457 + const spy = vi.spyOn(object, 'method') 458 + object.method() 459 + expect(vi.isMockFunction(object.method)).toBe(true) 460 + expect(spy.mock.calls).toHaveLength(1) 461 + spy.mockRestore() 462 + expect(vi.isMockFunction(object.method)).toBe(false) 463 + expect(spy.mock.calls).toHaveLength(0) 464 + }) 465 + 466 + test('vi.spyOn() restores the original method when vi.restoreAllMocks() is called', () => { 467 + const object = createObject() 468 + const spy = vi.spyOn(object, 'method') 469 + object.method() 470 + expect(vi.isMockFunction(object.method)).toBe(true) 471 + expect(spy.mock.calls).toHaveLength(1) 472 + vi.restoreAllMocks() 473 + expect(vi.isMockFunction(object.method)).toBe(false) 474 + // unlike vi.mockRestore(), the state is not cleared 475 + // this is important for module mocking 476 + expect(spy.mock.calls).toHaveLength(1) 477 + }) 478 + 479 + test('vi.spyOn() can respy the metthod with new state when vi.restoreAllMocks() is called', () => { 480 + const object = createObject() 481 + const spy1 = vi.spyOn(object, 'method').mockImplementation(() => 100) 482 + 483 + expect(object.method()).toBe(100) 484 + expect(spy1.mock.calls).toHaveLength(1) 485 + vi.restoreAllMocks() 486 + 487 + const spy2 = vi.spyOn(object, 'method').mockImplementation(() => 33) 488 + expect(object.method()).toBe(33) 489 + expect(spy2.mock.calls).toHaveLength(1) 490 + }) 491 + 492 + test('vi.spyOn() restores the original getter when .mockRestore() is called', () => { 493 + const object = createObject() 494 + const spy = vi.spyOn(object, 'getter', 'get').mockImplementation(() => 100) 495 + 496 + expect(object.getter).toBe(100) 497 + expect(spy.mock.calls).toHaveLength(1) 498 + spy.mockRestore() 499 + 500 + expect(spy.mock.calls).toHaveLength(0) 501 + expect(object.getter).toBe(42) 502 + }) 503 + 504 + test('vi.spyOn() restores the original getter when vi.restoreAllMocks() is called', () => { 505 + const object = createObject() 506 + const spy = vi.spyOn(object, 'getter', 'get').mockImplementation(() => 100) 507 + 508 + expect(object.getter).toBe(100) 509 + expect(spy.mock.calls).toHaveLength(1) 510 + vi.restoreAllMocks() 511 + 512 + // unlike vi.mockRestore(), the state is not cleared 513 + // this is important for module mocking 514 + expect(spy.mock.calls).toHaveLength(1) 515 + expect(object.getter).toBe(42) 516 + }) 517 + 518 + test('vi.spyOn() can respy the getter with new state when vi.restoreAllMocks() is called', () => { 519 + const object = createObject() 520 + const spy1 = vi.spyOn(object, 'getter', 'get').mockImplementation(() => 100) 521 + 522 + expect(object.getter).toBe(100) 523 + expect(spy1.mock.calls).toHaveLength(1) 524 + vi.restoreAllMocks() 525 + 526 + const spy2 = vi.spyOn(object, 'getter', 'get').mockImplementation(() => 33) 527 + expect(object.getter).toBe(33) 528 + expect(spy2.mock.calls).toHaveLength(1) 529 + }) 530 + 531 + test('vi.spyOn() restores the original setter when .mockRestore() is called', () => { 532 + const object = createObject() 533 + const spy = vi.spyOn(object, 'getter', 'set').mockImplementation(() => { 534 + // do nothing 535 + }) 536 + 537 + object.getter = 100 538 + 539 + expect(object.getter).toBe(42) // getter was not overriden 540 + expect(spy.mock.calls).toHaveLength(1) 541 + spy.mockRestore() 542 + 543 + object.getter = 33 544 + 545 + expect(spy.mock.calls).toHaveLength(0) 546 + expect(object.getter).toBe(33) 547 + }) 548 + 549 + test('vi.spyOn() restores the original getter when vi.restoreAllMocks() is called', () => { 550 + const object = createObject() 551 + const spy = vi.spyOn(object, 'getter', 'set').mockImplementation(() => { 552 + // do nothing 553 + }) 554 + 555 + object.getter = 100 556 + 557 + expect(object.getter).toBe(42) // getter was not overriden 558 + expect(spy.mock.calls).toHaveLength(1) 559 + vi.restoreAllMocks() 560 + 561 + // unlike vi.mockRestore(), the state is not cleared 562 + // this is important for module mocking 563 + expect(spy.mock.calls).toHaveLength(1) 564 + 565 + object.getter = 33 566 + 567 + expect(object.getter).toBe(33) 568 + }) 569 + 570 + test('vi.spyOn() can respy the getter with new state when vi.restoreAllMocks() is called', () => { 571 + const object = createObject() 572 + const spy1 = vi.spyOn(object, 'getter', 'set').mockImplementation(() => { 573 + // do nothing 574 + }) 575 + 576 + object.getter = 100 577 + 578 + expect(object.getter).toBe(42) 579 + expect(spy1.mock.calls).toHaveLength(1) 580 + vi.restoreAllMocks() 581 + 582 + let called = false 583 + const spy2 = vi.spyOn(object, 'getter', 'set').mockImplementation(() => { 584 + called = true 585 + }) 586 + 587 + object.getter = 84 588 + 589 + expect(called).toBe(true) 590 + expect(object.getter).toBe(42) 591 + expect(spy2.mock.calls).toHaveLength(1) 592 + }) 593 + }) 594 + 595 + function assertStateEmpty(state: MockContext<any>) { 596 + expect(state.calls).toHaveLength(0) 597 + expect(state.results).toHaveLength(0) 598 + expect(state.settledResults).toHaveLength(0) 599 + expect(state.contexts).toHaveLength(0) 600 + expect(state.instances).toHaveLength(0) 601 + expect(state.lastCall).toBe(undefined) 602 + expect(state.invocationCallOrder).toEqual([]) 603 + } 604 + 605 + function createObject() { 606 + let getterValue = 42 607 + return { 608 + Class: class {}, 609 + method() { 610 + return 42 611 + }, 612 + async() { 613 + return Promise.resolve(42) 614 + }, 615 + get getter() { 616 + return getterValue 617 + }, 618 + set getter(value: number) { 619 + getterValue = value 620 + }, 621 + } 622 + }
+1 -1
packages/browser/src/client/tester/tester.ts
··· 109 109 const mocker = new VitestBrowserClientMocker( 110 110 interceptor, 111 111 rpc, 112 - SpyModule.spyOn, 112 + SpyModule.createMockInstance, 113 113 { 114 114 root: getBrowserState().viteConfig.root, 115 115 },
+4 -4
packages/vitest/src/runtime/moduleRunner/moduleMocker.ts
··· 276 276 public mockObject( 277 277 object: Record<string | symbol, any>, 278 278 mockExports: Record<string | symbol, any> = {}, 279 - behavior: MockedModuleType = 'automock', 279 + behavior: 'automock' | 'autospy' = 'automock', 280 280 ): Record<string | symbol, any> { 281 - const spyOn = this.spyModule?.spyOn 282 - if (!spyOn) { 281 + const createMockInstance = this.spyModule?.createMockInstance 282 + if (!createMockInstance) { 283 283 throw this.createError( 284 284 '[vitest] `spyModule` is not defined. This is a Vitest error. Please open a new issue with reproduction.', 285 285 ) ··· 287 287 return mockObject( 288 288 { 289 289 globalConstructors: this.primitives, 290 - spyOn, 290 + createMockInstance, 291 291 type: behavior, 292 292 }, 293 293 object,