[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(spy): properly inherit implementation's `length` (#8778)

authored by

Vladimir and committed by
GitHub
(Oct 23, 2025, 5:55 PM +0200) d4c2b280 588f7685

+82 -4
+18
docs/api/mock.md
··· 20 20 21 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 + ::: warning IMPORTANT 24 + Vitest spies inherit implementation's `length` property. This means that `length` can be different from the original implementation: 25 + 26 + ```ts 27 + const example = { 28 + fn(arg1, arg2) { 29 + // ... 30 + } 31 + } 32 + 33 + const fn = vi.spyOn(example, 'fn') 34 + fn.length // == 2 35 + 36 + fn.mockImplementation(() => {}) 37 + fn.length // == 0 38 + ``` 39 + ::: 40 + 23 41 ::: tip 24 42 The custom function implementation in the types below is marked with a generic `<T>`. 25 43 :::
+19
packages/spy/src/index.ts
··· 494 494 if (original) { 495 495 copyOriginalStaticProperties(namedObject[name], original) 496 496 } 497 + let overrideLength: number | undefined 498 + Object.defineProperty(namedObject[name], 'length', { 499 + configurable: true, 500 + get: () => { 501 + if (overrideLength != null) { 502 + return overrideLength 503 + } 504 + 505 + const implementation = config.onceMockImplementations[0] 506 + || config.mockImplementation 507 + || prototypeConfig?.onceMockImplementations[0] 508 + || prototypeConfig?.mockImplementation 509 + || original 510 + return implementation?.length ?? 0 511 + }, 512 + set: (length: number) => { 513 + overrideLength = length 514 + }, 515 + }) 497 516 return namedObject[name] 498 517 } 499 518
+2 -2
test/browser/fixtures/mocking/import-mock.test.ts
··· 12 12 13 13 // creates a new mocked function with no formal arguments. 14 14 expect(example.square.name).toEqual('square') 15 - expect(example.square.length).toEqual(0) 15 + expect(example.square.length).toEqual(2) 16 16 17 17 // async functions get the same treatment as standard synchronous functions. 18 18 expect(example.asyncSquare.name).toEqual('asyncSquare') 19 - expect(example.asyncSquare.length).toEqual(0) 19 + expect(example.asyncSquare.length).toEqual(2) 20 20 21 21 // creates a new class with the same interface, member functions and properties are mocked. 22 22 expect(example.someClasses.constructor.name).toEqual('Bar')
+2 -2
test/core/test/mocking/automocking.spec.ts
··· 13 13 14 14 // creates a new mocked function with no formal arguments. 15 15 expect(example.square.name).toEqual('square') 16 - expect(example.square.length).toEqual(0) 16 + expect(example.square.length).toEqual(2) 17 17 18 18 // async functions get the same treatment as standard synchronous functions. 19 19 expect(example.asyncSquare.name).toEqual('asyncSquare') 20 - expect(example.asyncSquare.length).toEqual(0) 20 + expect(example.asyncSquare.length).toEqual(2) 21 21 22 22 // creates a new class with the same interface, member functions and properties are mocked. 23 23 expect(example.someClasses.constructor.name).toEqual('Bar')
+24
test/core/test/mocking/vi-fn.test.ts
··· 20 20 }).toThrowError() 21 21 }) 22 22 23 + test('vi.fn() has correct length', () => { 24 + const fn0 = vi.fn(() => {}) 25 + expect(fn0.length).toBe(0) 26 + 27 + const fnArgs = vi.fn((..._args) => {}) 28 + expect(fnArgs.length).toBe(0) 29 + 30 + const fn1 = vi.fn((_arg1) => {}) 31 + expect(fn1.length).toBe(1) 32 + 33 + const fn2 = vi.fn((_arg1, _arg2) => {}) 34 + expect(fn2.length).toBe(2) 35 + 36 + const fn3 = vi.fn((_arg1, _arg2, _arg3) => {}) 37 + expect(fn3.length).toBe(3) 38 + }) 39 + 40 + test('vi.fn() has overridable length', () => { 41 + const fn0 = vi.fn(() => {}) 42 + // @ts-expect-error TS doesn't allow override 43 + fn0.length = 5 44 + expect(fn0.length).toBe(5) 45 + }) 46 + 23 47 describe('vi.fn() state', () => { 24 48 // TODO: test when calls is not empty 25 49 test('vi.fn() clears calls without a custom implementation', () => {
+17
test/core/test/mocking/vi-spyOn.test.ts
··· 1 1 import type { MockContext } from 'vitest' 2 2 import { describe, expect, test, vi } from 'vitest' 3 3 4 + test('vi.fn() has correct length', () => { 5 + const fn0 = vi.spyOn({ fn: () => {} }, 'fn') 6 + expect(fn0.length).toBe(0) 7 + 8 + const fnArgs = vi.spyOn({ fn: (..._args: any[]) => {} }, 'fn') 9 + expect(fnArgs.length).toBe(0) 10 + 11 + const fn1 = vi.spyOn({ fn: (_arg1: any) => {} }, 'fn') 12 + expect(fn1.length).toBe(1) 13 + 14 + const fn2 = vi.spyOn({ fn: (_arg1: any, _arg2: any) => {} }, 'fn') 15 + expect(fn2.length).toBe(2) 16 + 17 + const fn3 = vi.spyOn({ fn: (_arg1: any, _arg2: any, _arg3: any) => {} }, 'fn') 18 + expect(fn3.length).toBe(3) 19 + }) 20 + 4 21 describe('vi.spyOn() state', () => { 5 22 test('vi.spyOn() spies on an object and tracks the calls', () => { 6 23 const object = createObject()