in packages/jest-mock/src/index.ts [591:827]
private _makeComponent<T extends UnknownFunction>(
metadata: MockFunctionMetadata<T, 'regexp'>,
restore?: () => void,
): RegExp;
private _makeComponent<T extends UnknownFunction>(
metadata: MockFunctionMetadata<
T,
'constant' | 'collection' | 'null' | 'undefined'
>,
restore?: () => void,
): T;
private _makeComponent<T extends UnknownFunction>(
metadata: MockFunctionMetadata<T, 'function'>,
restore?: () => void,
): Mock<T>;
private _makeComponent<T extends UnknownFunction>(
metadata: MockFunctionMetadata<T>,
restore?: () => void,
):
| Record<string, any>
| Array<unknown>
| RegExp
| ReturnType<T>
| undefined
| Mock<T> {
if (metadata.type === 'object') {
return new this._environmentGlobal.Object();
} else if (metadata.type === 'array') {
return new this._environmentGlobal.Array();
} else if (metadata.type === 'regexp') {
return new this._environmentGlobal.RegExp('');
} else if (
metadata.type === 'constant' ||
metadata.type === 'collection' ||
metadata.type === 'null' ||
metadata.type === 'undefined'
) {
return metadata.value;
} else if (metadata.type === 'function') {
const prototype =
(metadata.members &&
metadata.members.prototype &&
metadata.members.prototype.members) ||
{};
const prototypeSlots = this._getSlots(prototype);
const mocker = this;
const mockConstructor = matchArity(function (
this: ReturnType<T>,
...args: Parameters<T>
) {
const mockState = mocker._ensureMockState(f);
const mockConfig = mocker._ensureMockConfig(f);
mockState.instances.push(this);
mockState.contexts.push(this);
mockState.calls.push(args);
// Create and record an "incomplete" mock result immediately upon
// calling rather than waiting for the mock to return. This avoids
// issues caused by recursion where results can be recorded in the
// wrong order.
const mockResult: MockFunctionResult = {
type: 'incomplete',
value: undefined,
};
mockState.results.push(mockResult);
mockState.invocationCallOrder.push(mocker._invocationCallCounter++);
// Will be set to the return value of the mock if an error is not thrown
let finalReturnValue;
// Will be set to the error that is thrown by the mock (if it throws)
let thrownError;
// Will be set to true if the mock throws an error. The presence of a
// value in `thrownError` is not a 100% reliable indicator because a
// function could throw a value of undefined.
let callDidThrowError = false;
try {
// The bulk of the implementation is wrapped in an immediately
// executed arrow function so the return value of the mock function
// can be easily captured and recorded, despite the many separate
// return points within the logic.
finalReturnValue = (() => {
if (this instanceof f) {
// This is probably being called as a constructor
prototypeSlots.forEach(slot => {
// Copy prototype methods to the instance to make
// it easier to interact with mock instance call and
// return values
if (prototype[slot].type === 'function') {
// @ts-expect-error no index signature
const protoImpl = this[slot];
// @ts-expect-error no index signature
this[slot] = mocker.generateFromMetadata(prototype[slot]);
// @ts-expect-error no index signature
this[slot]._protoImpl = protoImpl;
}
});
// Run the mock constructor implementation
const mockImpl = mockConfig.specificMockImpls.length
? mockConfig.specificMockImpls.shift()
: mockConfig.mockImpl;
return mockImpl && mockImpl.apply(this, arguments);
}
// If mockImplementationOnce()/mockImplementation() is last set,
// implementation use the mock
let specificMockImpl = mockConfig.specificMockImpls.shift();
if (specificMockImpl === undefined) {
specificMockImpl = mockConfig.mockImpl;
}
if (specificMockImpl) {
return specificMockImpl.apply(this, arguments);
}
// Otherwise use prototype implementation
if (f._protoImpl) {
return f._protoImpl.apply(this, arguments);
}
return undefined;
})();
} catch (error) {
// Store the thrown error so we can record it, then re-throw it.
thrownError = error;
callDidThrowError = true;
throw error;
} finally {
// Record the result of the function.
// NOTE: Intentionally NOT pushing/indexing into the array of mock
// results here to avoid corrupting results data if mockClear()
// is called during the execution of the mock.
// @ts-expect-error reassigning 'incomplete'
mockResult.type = callDidThrowError ? 'throw' : 'return';
mockResult.value = callDidThrowError ? thrownError : finalReturnValue;
}
return finalReturnValue;
},
metadata.length || 0);
const f = this._createMockFunction(metadata, mockConstructor) as Mock;
f._isMockFunction = true;
f.getMockImplementation = () =>
this._ensureMockConfig(f).mockImpl as UnknownFunction;
if (typeof restore === 'function') {
this._spyState.add(restore);
}
this._mockState.set(f, this._defaultMockState());
this._mockConfigRegistry.set(f, this._defaultMockConfig());
Object.defineProperty(f, 'mock', {
configurable: false,
enumerable: true,
get: () => this._ensureMockState(f),
set: val => this._mockState.set(f, val),
});
f.mockClear = () => {
this._mockState.delete(f);
return f;
};
f.mockReset = () => {
f.mockClear();
this._mockConfigRegistry.delete(f);
return f;
};
f.mockRestore = () => {
f.mockReset();
return restore ? restore() : undefined;
};
f.mockReturnValueOnce = (value: ReturnType<T>) =>
// next function call will return this value or default return value
f.mockImplementationOnce(() => value);
f.mockResolvedValueOnce = (value: ResolveType<T>) =>
f.mockImplementationOnce(() => Promise.resolve(value));
f.mockRejectedValueOnce = (value: unknown) =>
f.mockImplementationOnce(() => Promise.reject(value));
f.mockReturnValue = (value: ReturnType<T>) =>
// next function call will return specified return value or this one
f.mockImplementation(() => value);
f.mockResolvedValue = (value: ResolveType<T>) =>
f.mockImplementation(() => Promise.resolve(value));
f.mockRejectedValue = (value: unknown) =>
f.mockImplementation(() => Promise.reject(value));
f.mockImplementationOnce = (fn: UnknownFunction) => {
// next function call will use this mock implementation return value
// or default mock implementation return value
const mockConfig = this._ensureMockConfig(f);
mockConfig.specificMockImpls.push(fn);
return f;
};
f.mockImplementation = (fn: UnknownFunction) => {
// next function call will use mock implementation return value
const mockConfig = this._ensureMockConfig(f);
mockConfig.mockImpl = fn;
return f;
};
f.mockReturnThis = () =>
f.mockImplementation(function (this: ReturnType<T>) {
return this;
});
f.mockName = (name: string) => {
if (name) {
const mockConfig = this._ensureMockConfig(f);
mockConfig.mockName = name;
}
return f;
};
f.getMockName = () => {
const mockConfig = this._ensureMockConfig(f);
return mockConfig.mockName || 'jest.fn()';
};
if (metadata.mockImpl) {
f.mockImplementation(metadata.mockImpl);
}
return f;
} else {
const unknownType = metadata.type || 'undefined type';
throw new Error(`Unrecognized type ${unknownType}`);
}
}