Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831import type { Classes, Constructable, Methods, Mock, MockConfig, MockContext, MockInstanceOption, MockProcedureContext, MockResult, MockReturnType, MockSettledResult, Procedure, Properties,} from './types'
export function isMockFunction(fn: any): fn is Mock { return typeof fn === 'function' && '_isMockFunction' in fn && fn._isMockFunction === true}
const MOCK_RESTORE = new Set<() => void>()// Jest keeps the state in a separate WeakMap which is good for memory,// but it makes the state slower to access and return different values// if you stored it before calling `mockClear` where it will be recreatedconst DIRTY_MOCK_STATES = new Set<Mock<Procedure | Constructable>>()const addDirtyMock = DIRTY_MOCK_STATES.add.bind(DIRTY_MOCK_STATES)const REGISTERED_MOCKS = new Set<WeakRef<Mock<Procedure | Constructable>>>()const MOCK_FINALIZER = new FinalizationRegistry<WeakRef<Mock<Procedure | Constructable>>>((ref) => { REGISTERED_MOCKS.delete(ref)})const MOCK_CONFIGS = new WeakMap<Mock<Procedure | Constructable>, MockConfig>()const MOCKS_BY_STATE = new WeakMap<MockContext, Mock<Procedure | Constructable>>()
export function createMockInstance( options: MockInstanceOption = {},): Mock<Procedure | Constructable> { const { originalImplementation, restore, mockImplementation, resetToMockImplementation, resetToMockName, } = options
if (restore) { MOCK_RESTORE.add(restore) }
const config = getDefaultConfig(originalImplementation) const state = getDefaultState()
const mock = createMock({ config, state, ...options, }) const mockLength = (mockImplementation || originalImplementation)?.length ?? 0 Object.defineProperty(mock, 'length', { writable: true, enumerable: false, value: mockLength, configurable: true, }) // inherit the default name so it appears in snapshots and logs // this is used by `vi.spyOn()` for better debugging. // when `vi.fn()` is called, we just use the default string if (resetToMockName) { config.mockName = mock.name || 'vi.fn()' } MOCK_CONFIGS.set(mock, config) MOCKS_BY_STATE.set(state, mock) const ref = new WeakRef(mock) REGISTERED_MOCKS.add(ref) MOCK_FINALIZER.register(mock, ref)
mock._isMockFunction = true mock.getMockImplementation = () => { // Jest only returns `config.mockImplementation` here, // but we think it makes sense to return what the next function will be called return config.onceMockImplementations[0] || config.mockImplementation }
// keep the prototype chain in sync with the implementation the next // construction will use, so it is correct before any `new` call. // automocked classes are skipped: their methods are pre-mocked // on `mock.prototype` const updateMockPrototype = () => { if (!options.prototypeMembers?.length) { reparentMockPrototype( mock, config.onceMockImplementations[0] || config.mockImplementation || originalImplementation, ) } }
Object.defineProperty(mock, 'mock', { configurable: false, enumerable: true, writable: false, value: state, })
mock.mockImplementation = function mockImplementation(implementation) { config.mockImplementation = implementation updateMockPrototype() return mock }
mock.mockImplementationOnce = function mockImplementationOnce(implementation) { config.onceMockImplementations.push(implementation) updateMockPrototype() return mock }
mock.withImplementation = function withImplementation(implementation, callback) { const previousImplementation = config.mockImplementation const previousOnceImplementations = config.onceMockImplementations
const reset = () => { config.mockImplementation = previousImplementation config.onceMockImplementations = previousOnceImplementations updateMockPrototype() }
config.mockImplementation = implementation config.onceMockImplementations = [] updateMockPrototype()
const returnValue = callback()
if ( typeof returnValue === 'object' && typeof (returnValue as Promise<any>)?.then === 'function' ) { return (returnValue as Promise<any>).then(() => { reset() return mock }) as any } else { reset() } return mock }
mock.mockReturnThis = function mockReturnThis() { return mock.mockImplementation(function (this: any) { return this }) }
mock.mockReturnValue = function mockReturnValue(value) { return mock.mockImplementation(function () { if (new.target) { throwConstructorError('mockReturnValue') }
return value }) }
mock.mockReturnValueOnce = function mockReturnValueOnce(value) { return mock.mockImplementationOnce(function () { if (new.target) { throwConstructorError('mockReturnValueOnce') }
return value }) }
mock.mockThrow = function mockThrow(value) { // oxlint-disable-next-line prefer-arrow-callback return mock.mockImplementation(function () { throw value }) }
mock.mockThrowOnce = function mockThrowOnce(value) { // oxlint-disable-next-line prefer-arrow-callback return mock.mockImplementationOnce(function () { throw value }) }
mock.mockResolvedValue = function mockResolvedValue(value) { return mock.mockImplementation(function () { if (new.target) { throwConstructorError('mockResolvedValue') }
return Promise.resolve(value) }) }
mock.mockResolvedValueOnce = function mockResolvedValueOnce(value) { return mock.mockImplementationOnce(function () { if (new.target) { throwConstructorError('mockResolvedValueOnce') }
return Promise.resolve(value) }) }
mock.mockRejectedValue = function mockRejectedValue(value) { return mock.mockImplementation(function () { if (new.target) { throwConstructorError('mockRejectedValue') }
return Promise.reject(value) }) }
mock.mockRejectedValueOnce = function mockRejectedValueOnce(value) { return mock.mockImplementationOnce(function () { if (new.target) { throwConstructorError('mockRejectedValueOnce') }
return Promise.reject(value) }) }
mock.mockClear = function mockClear() { state.calls = [] state.contexts = [] state.instances = [] state.invocationCallOrder = [] state.results = [] state.settledResults = [] DIRTY_MOCK_STATES.delete(mock) return mock }
mock.mockReset = function mockReset() { mock.mockClear() config.mockImplementation = resetToMockImplementation ? mockImplementation : undefined config.mockName = resetToMockName ? mock.name || 'vi.fn()' : 'vi.fn()' config.onceMockImplementations = [] updateMockPrototype() return mock }
mock.mockRestore = function mockRestore() { mock.mockReset() return restore?.() }
mock.mockName = function mockName(name: string) { if (typeof name === 'string') { config.mockName = name } return mock }
mock.getMockName = function getMockName() { return config.mockName || 'vi.fn()' }
if (Symbol.dispose) { mock[Symbol.dispose] = () => mock.mockRestore() }
if (mockImplementation) { mock.mockImplementation(mockImplementation) } else { // vi.spyOn() has no mock implementation, chain the original one updateMockPrototype() }
return mock}
export function fn<T extends Procedure | Constructable = Procedure>( originalImplementation?: T,): Mock<T> { // if the function is already a mock, just return the same function, // similarly to how vi.spyOn() works if (originalImplementation != null && isMockFunction(originalImplementation)) { return originalImplementation as Mock<T> }
return createMockInstance({ // we pass this down so getMockImplementation() always returns the value mockImplementation: originalImplementation, // special case so that .mockReset() resets the value to // the the originalImplementation instead of () => undefined resetToMockImplementation: true, }) as Mock<T>}
type SpyOnValue<T extends object, K extends keyof any> = K extends keyof Required<T> ? Required<T>[K] : (T & Record<K, unknown>)[K]
type SpyOnMethod<T extends object, K extends keyof any> = SpyOnValue<T, K> extends Constructable | Procedure ? SpyOnValue<T, K> : never
type SpyOnMethodKey<T extends object, K extends keyof any> = SpyOnValue<T, K> extends Constructable | Procedure ? K : never
export function spyOn<T extends object, S extends Properties<Required<T>>>( object: T, key: S, accessor: 'get',): Mock<() => T[S]>export function spyOn<T extends object, G extends Properties<Required<T>>>( object: T, key: G, accessor: 'set',): Mock<(arg: T[G]) => void>export function spyOn<T extends object, M extends Classes<Required<T>> | Methods<Required<T>>>( object: T, key: M,): Required<T>[M] extends Constructable | Procedure ? Mock<Required<T>[M]> : neverexport function spyOn<T extends object, K extends keyof any>( object: T, key: SpyOnMethodKey<T, K>,): Mock<SpyOnMethod<T, K>>export function spyOn<T extends object, K extends keyof any>( object: T, key: K, accessor?: 'get' | 'set',): Mock<Procedure | Constructable> { assert( object != null, 'The vi.spyOn() function could not find an object to spy upon. The first argument must be defined.', )
assert( typeof object === 'object' || typeof object === 'function', 'Vitest cannot spy on a primitive value.', )
const [originalDescriptorObject, originalDescriptor] = getDescriptor(object, key) || [] assert( originalDescriptor || key in object, `The property "${String(key)}" is not defined on the ${typeof object}.`, ) let accessType: 'get' | 'set' | 'value' = accessor || 'value' let ssr = false
// vite ssr support - actual function is stored inside a getter if ( accessType === 'value' && originalDescriptor && originalDescriptor.value == null && originalDescriptor.get ) { accessType = 'get' ssr = true }
let original: Procedure | undefined
if (originalDescriptor) { original = originalDescriptor[accessType] // weird Proxy edge case where descriptor's value is undefined, // but there's still a value on the object when called // https://github.com/vitest-dev/vitest/issues/9439 if (original == null && accessType === 'value') { original = object[key as unknown as keyof T] as unknown as Procedure } } else if (accessType !== 'value') { original = () => object[key as unknown as keyof T] } else { original = object[key as unknown as keyof T] as unknown as Procedure }
const originalImplementation = ssr && original ? original() : original const originalType = typeof originalImplementation
assert( // allow only functions originalType === 'function' || // or allow getter/setter on a static value, // e.g. spyOn({ value: 3 }, 'value', 'get') (accessType !== 'value' && original == null), `vi.spyOn() can only spy on a function. Received ${originalType}.`, )
if (isMockFunction(originalImplementation)) { return originalImplementation }
const reassign = (cb: any) => { const { value, ...desc } = originalDescriptor || { configurable: true, writable: true, } if (accessType !== 'value') { delete desc.writable // getter/setter can't have writable attribute at all } ;(desc as PropertyDescriptor)[accessType] = cb Object.defineProperty(object, key, desc) }
const restore = () => { // if method is defined on the prototype, we can just remove it from // the current object instead of redefining a copy of it if (originalDescriptorObject !== object) { Reflect.deleteProperty(object, key) } else if (originalDescriptor && !original) { Object.defineProperty(object, key, originalDescriptor) } else { reassign(original) } }
const mock = createMockInstance({ restore, originalImplementation, resetToMockName: true, })
try { reassign(ssr ? () => mock : mock) } catch (error) { if ( error instanceof TypeError && Symbol.toStringTag && (object as any)[Symbol.toStringTag] === 'Module' && (error.message.includes('Cannot redefine property') || error.message.includes('Cannot replace module namespace') || error.message.includes("can't redefine non-configurable property")) ) { throw new TypeError( `Cannot spy on export "${String(key)}". Module namespace is not configurable in ESM. See: https://vitest.dev/guide/mocking/modules#mocking-a-module`, { cause: error }, ) }
throw error }
return mock}
function getDescriptor( obj: any, method: string | symbol | number,): [any, PropertyDescriptor] | undefined { const objDescriptor = Object.getOwnPropertyDescriptor(obj, method) if (objDescriptor) { return [obj, objDescriptor] } let currentProto = Object.getPrototypeOf(obj) while (currentProto !== null) { const descriptor = Object.getOwnPropertyDescriptor(currentProto, method) if (descriptor) { return [currentProto, descriptor] } currentProto = Object.getPrototypeOf(currentProto) }}
function assert(condition: any, message: string): asserts condition { if (!condition) { throw new Error(message) }}
let invocationCallCounter = 1
function createMock({ state, config, name: mockName, prototypeState, prototypeConfig, keepMembersImplementation, mockImplementation, prototypeMembers = [],}: MockInstanceOption & { state: MockContext config: MockConfig}) { const original = config.mockOriginal // init with vi.spyOn(obj, 'Klass') const pseudoOriginal = mockImplementation // init with vi.fn(Klass) const name = (mockName || original?.name || 'Mock') as string const noopImplementation = function () {} const prototypeMock = prototypeState && MOCKS_BY_STATE.get(prototypeState) const namedObject: Record<string, Mock<Procedure | Constructable>> = { // to keep the name of the function intact [name]: function (this: any, ...args: any[]) { addDirtyMock(namedObject[name]) if (prototypeMock) { addDirtyMock(prototypeMock) } registerCalls(args, state, prototypeState) registerInvocationOrder(invocationCallCounter++, state, prototypeState)
const result = { type: 'incomplete', value: undefined, } as MockResult<Procedure>
const settledResult = { type: 'incomplete', value: undefined, } as MockSettledResult<Procedure>
registerResult(result, state, prototypeState) registerSettledResult(settledResult, state, prototypeState)
const context = new.target ? undefined : this const [instanceIndex, instancePrototypeIndex] = registerInstance( context, state, prototypeState, ) const [contextIndex, contextPrototypeIndex] = registerContext(context, state, prototypeState)
const implementation: Procedure | Constructable = config.onceMockImplementations.shift() || config.mockImplementation || prototypeConfig?.onceMockImplementations.shift() || prototypeConfig?.mockImplementation || original || noopImplementation
let returnValue let thrownValue let didThrow = false
try { if (new.target) { // the prototype chain is already prepared when the implementation // is registered, but a consumed `mockImplementationOnce` can change // which implementation this construction uses if (prototypeMembers.length === 0) { reparentMockPrototype( // oxlint-disable-next-line typescript/no-use-before-define mock, implementation === noopImplementation ? undefined : implementation, ) } returnValue = Reflect.construct(implementation, args, new.target)
// jest calls this before the implementation, but we have to resolve this _after_ // because we cannot do it before the `Reflect.construct` called the custom implementation. // fortunately, the constructor is always an empty function because `prototypeMethods` // are only used by the automocker, so this doesn't matter for (const prop of prototypeMembers) { const prototypeMock = returnValue[prop] // the method was overridden because of inheritance, ignore it // oxlint-disable-next-line typescript/no-use-before-define if (prototypeMock !== mock.prototype[prop]) { continue }
const isMock = isMockFunction(prototypeMock) const prototypeState = isMock ? prototypeMock.mock : undefined const prototypeConfig = isMock ? MOCK_CONFIGS.get(prototypeMock) : undefined returnValue[prop] = createMockInstance({ originalImplementation: keepMembersImplementation ? prototypeConfig?.mockOriginal : undefined, prototypeState, prototypeConfig, keepMembersImplementation, }) } } else { returnValue = (implementation as Procedure).apply(this, args) } } catch (error: any) { thrownValue = error didThrow = true if (error instanceof TypeError && error.message.includes('is not a constructor')) { 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.`, ) } throw error } finally { if (didThrow) { result.type = 'throw' result.value = thrownValue
settledResult.type = 'rejected' settledResult.value = thrownValue } else { result.type = 'return' result.value = returnValue
if (new.target) { state.contexts[contextIndex - 1] = returnValue state.instances[instanceIndex - 1] = returnValue
if (contextPrototypeIndex != null && prototypeState) { prototypeState.contexts[contextPrototypeIndex - 1] = returnValue } if (instancePrototypeIndex != null && prototypeState) { prototypeState.instances[instancePrototypeIndex - 1] = returnValue } }
if (returnValue instanceof Promise) { returnValue.then( (settledValue) => { settledResult.type = 'fulfilled' settledResult.value = settledValue }, (rejectedValue) => { settledResult.type = 'rejected' settledResult.value = rejectedValue }, ) } else { settledResult.type = 'fulfilled' settledResult.value = returnValue } } }
return returnValue } as Mock, } const mock = namedObject[name] as Mock<Procedure | Constructable> const copyPropertiesFrom = original || pseudoOriginal if (copyPropertiesFrom) { copyOriginalStaticProperties(mock, copyPropertiesFrom) } return mock}
// puts the implementation's prototype behind `mock.prototype` so instances// see prototype methods both during and after construction, while properties// assigned on `mock.prototype` still shadow themfunction reparentMockPrototype( mock: Mock<Procedure | Constructable>, implementation: Procedure | Constructable | undefined,) { const mockPrototype = mock.prototype if (mockPrototype == null) { return } // an implementation without a usable prototype (reset mock, arrow or bound // function) reverts the chain to `Object.prototype`, the parent every mock // is created with const parent = (implementation as Constructable | undefined)?.prototype ?? Object.prototype if (mockPrototype !== parent && Object.getPrototypeOf(mockPrototype) !== parent) { Object.setPrototypeOf(mockPrototype, parent) }}
function registerCalls(args: unknown[], state: MockContext, prototypeState?: MockContext) { state.calls.push(args) prototypeState?.calls.push(args)}
function registerInvocationOrder(order: number, state: MockContext, prototypeState?: MockContext) { state.invocationCallOrder.push(order) prototypeState?.invocationCallOrder.push(order)}
function registerResult( result: MockResult<Procedure>, state: MockContext, prototypeState?: MockContext,) { state.results.push(result) prototypeState?.results.push(result)}
function registerSettledResult( result: MockSettledResult<Procedure>, state: MockContext, prototypeState?: MockContext,) { state.settledResults.push(result) prototypeState?.settledResults.push(result)}
function registerInstance( instance: MockReturnType<Procedure>, state: MockContext, prototypeState?: MockContext,) { const instanceIndex = state.instances.push(instance) const instancePrototypeIndex = prototypeState?.instances.push(instance) return [instanceIndex, instancePrototypeIndex] as const}
function registerContext( context: MockProcedureContext<Procedure>, state: MockContext, prototypeState?: MockContext,) { const contextIndex = state.contexts.push(context) const contextPrototypeIndex = prototypeState?.contexts.push(context) return [contextIndex, contextPrototypeIndex] as const}
function copyOriginalStaticProperties( mock: Mock<Procedure | Constructable>, original: Procedure | Constructable,) { const { properties, descriptors } = getAllProperties(original)
for (const key of properties) { const descriptor = descriptors[key]! const mockDescriptor = getDescriptor(mock, key) if (mockDescriptor) { continue }
Object.defineProperty(mock, key, descriptor) }}
const ignoreProperties = new Set<string | symbol>([ 'length', 'name', 'prototype', Symbol.for('nodejs.util.promisify.custom'),])
function getAllProperties(original: Procedure | Constructable) { const properties = new Set<string | symbol>() const descriptors: Record<string | symbol, PropertyDescriptor | undefined> = {} while (original && original !== Object.prototype && original !== Function.prototype) { const ownProperties = [ ...Object.getOwnPropertyNames(original), ...Object.getOwnPropertySymbols(original), ] for (const prop of ownProperties) { if (descriptors[prop] || ignoreProperties.has(prop)) { continue } properties.add(prop) descriptors[prop] = Object.getOwnPropertyDescriptor(original, prop) } original = Object.getPrototypeOf(original) } return { properties, descriptors, }}
function getDefaultConfig(original?: Procedure | Constructable): MockConfig { return { mockImplementation: undefined, mockOriginal: original, mockName: 'vi.fn()', onceMockImplementations: [], }}
function getDefaultState(): MockContext { const state = { calls: [], contexts: [], instances: [], invocationCallOrder: [], settledResults: [], results: [], get lastCall() { return state.calls.at(-1) }, } return state}
export function restoreAllMocks(): void { for (const restore of MOCK_RESTORE) { restore() } MOCK_RESTORE.clear()}
export function clearAllMocks(): void { for (const mock of DIRTY_MOCK_STATES) { mock.mockClear() }}
export function resetAllMocks(): void { for (const ref of REGISTERED_MOCKS) { const mock = ref.deref() if (mock) { mock.mockReset() } else { REGISTERED_MOCKS.delete(ref) } }}
function throwConstructorError(shorthand: string): never { throw new TypeError( `Cannot use \`${shorthand}\` when called with \`new\`. Use \`mockImplementation\` with a \`class\` keyword instead. See https://vitest.dev/api/mock#class-support for more information.`, )}
export type { Constructable, MaybeMocked, MaybeMockedConstructor, MaybeMockedDeep, MaybePartiallyMocked, MaybePartiallyMockedDeep, Mock, MockContext, Mocked, MockedClass, MockedFunction, MockedFunctionDeep, MockedObject, MockedObjectDeep, MockInstance, MockInstanceOption, MockParameters, MockProcedureContext, MockResult, MockResultIncomplete, MockResultReturn, MockResultThrow, MockReturnType, MockSettledResult, MockSettledResultFulfilled, MockSettledResultIncomplete, MockSettledResultRejected, PartiallyMockedFunction, PartiallyMockedFunctionDeep, PartialMock, Procedure,} from './types'