/* * Copyright (c) 2022-2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import ExtendInterface from './ExtendInterface.js'; import VerificationMode from './VerificationMode.js'; import { ArgumentMatchers } from './ArgumentMatchers.js'; class MockKit { constructor() { this.mFunctions = []; this.stubs = new Map(); this.recordCalls = new Map(); this.currentSetKey = new Map(); this.mockObj = null; this.recordMockedMethod = new Map(); this.mFunctions = []; this.stubs = new Map(); this.recordCalls = new Map(); this.currentSetKey = new Map(); this.mockObj = null; this.recordMockedMethod = new Map(); } init() { this.reset(); } reset() { this.mFunctions = []; this.stubs = new Map(); this.recordCalls = new Map(); this.currentSetKey = new Map(); this.mockObj = null; this.recordMockedMethod = new Map(); } clearAll() { this.reset(); } clear(obj) { if (!obj) { throw Error('Please enter an object to be cleaned'); } if (typeof (obj) !== 'object' && typeof (obj) !== 'function') { throw new Error('Not a object or static class'); } this.recordMockedMethod.forEach(function (value, key, map) { if (key) { obj[key] = value; } }); } ignoreMock(obj, method) { if (typeof (obj) !== 'object' && typeof (obj) !== 'function') { throw new Error('Not a object or static class'); } if (typeof (method) !== 'function') { throw new Error('Not a function'); } let og = this.recordMockedMethod.get(method.propName); if (og) { obj[method.propName] = og; this.recordMockedMethod.set(method.propName, undefined); } } extend(dest, source) { dest['stub'] = source['stub']; dest['afterReturn'] = source['afterReturn']; dest['afterReturnNothing'] = source['afterReturnNothing']; dest['afterAction'] = source['afterAction']; dest['afterThrow'] = source['afterThrow']; dest['stubMockedCall'] = source['stubMockedCall']; dest['clear'] = source['clear']; return dest; } stubApply(f, params, returnInfo) { let values = this.stubs.get(f); if (!values) { values = new Map(); } let key = params[0]; if (typeof key == 'undefined') { key = 'anonymous-mock-' + f.propName; } let matcher = new ArgumentMatchers(); if (matcher.matcheStubKey(key)) { key = matcher.matcheStubKey(key); if (key) { this.currentSetKey.set(f, key); } } values.set(key, returnInfo); this.stubs.set(f, values); } getReturnInfo(f, params) { let values = this.stubs.get(f); if (!values) { return undefined; } let retrunKet = params[0]; if (typeof retrunKet == 'undefined') { retrunKet = 'anonymous-mock-' + f.propName; } let stubSetKey = this.currentSetKey.get(f); if (stubSetKey && (typeof (retrunKet) !== 'undefined')) { retrunKet = stubSetKey; } let matcher = new ArgumentMatchers(); if (matcher.matcheReturnKey(params[0], undefined, stubSetKey) && matcher.matcheReturnKey(params[0], undefined, stubSetKey) !== stubSetKey) { retrunKet = params[0]; } values.forEach(function (value, key, map) { if (ArgumentMatchers.isRegExp(key) && matcher.matcheReturnKey(params[0], key)) { retrunKet = key; } }); return values.get(retrunKet); } findName(obj, value) { let properties = this.findProperties(obj); let name = ''; properties.filter((item) => (item !== 'caller' && item !== 'arguments')).forEach(function (va1, idx, array) { if (obj[va1] === value) { name = va1; } }); return name; } isFunctionFromPrototype(f, container, propName) { if (container.constructor !== Object && container.constructor.prototype !== container) { return container.constructor.prototype[propName] === f; } return false; } findProperties(obj, ...arg) { function getProperty(newObj) { if (newObj.__proto__ === null) { return []; } let properties = Object.getOwnPropertyNames(newObj); return [...properties, ...getProperty(newObj.__proto__)]; } return getProperty(obj); } recordMethodCall(originalMethod, args) { originalMethod['getName'] = function () { return this.name || this.toString().match(/function\s*([^(]*)\(/)[1]; }; let name = originalMethod.getName(); let arglistString = name + '(' + Array.from(args).toString() + ')'; let records = this.recordCalls.get(arglistString); if (!records) { records = 0; } records++; this.recordCalls.set(arglistString, records); } mockFunc(originalObject, originalMethod) { let tmp = this; this.originalMethod = originalMethod; const _this = this; let f = function () { let args = arguments; let action = tmp.getReturnInfo(f, args); if (originalMethod) { tmp.recordMethodCall(originalMethod, args); } if (action) { return action.apply(_this, args); } }; f.container = null || originalObject; f.original = originalMethod || null; if (originalObject && originalMethod) { if (typeof (originalMethod) !== 'function') { throw new Error('Not a function'); } var name = this.findName(originalObject, originalMethod); originalObject[name] = f; this.recordMockedMethod.set(name, originalMethod); f.propName = name; f.originalFromPrototype = this.isFunctionFromPrototype(f.original, originalObject, f.propName); } f.mocker = this; this.mFunctions.push(f); this.extend(f, new ExtendInterface(this)); return f; } verify(methodName, argsArray) { if (!methodName) { throw Error('not a function name'); } let a = this.recordCalls.get(methodName + '(' + argsArray.toString() + ')'); return new VerificationMode(a ? a : 0); } mockObject(object) { if (!object || typeof object === 'string') { throw Error(`this ${object} cannot be mocked`); } const _this = this; let mockedObject = {}; let keys = Reflect.ownKeys(object); keys.filter(key => (typeof Reflect.get(object, key)) === 'function') .forEach((key) => { mockedObject[key] = object[key]; mockedObject[key] = _this.mockFunc(mockedObject, mockedObject[key]); }); return mockedObject; } } function ifMockedFunction(f) { if (Object.prototype.toString.call(f) !== '[object Function]' && Object.prototype.toString.call(f) !== '[object AsyncFunction]') { throw Error('not a function'); } if (!f.stub) { throw Error('not a mock function'); } return true; } function when(f) { if (!ifMockedFunction(f)) { throw Error('not a mock function'); } return f.stub.bind(f); } function MockSetup(target, propertyName, descriptor) { const aboutToAppearOrigin = target.aboutToAppear; const setup = descriptor.value; target.aboutToAppear = function (...args) { if (target.__Param) { // copy attributes and params of the original context try { const map = target.__Param; for (const [key, val] of map) { this[key] = val; // 'this' refers to context of current function } } catch (e) { throw new Error(`Mock setup param error: ${e}`); } } if (setup) { // apply the mock content try { setup.apply(this); } catch (e) { throw new Error(`Mock setup apply error: ${e}`); } } if (aboutToAppearOrigin) { // append to aboutToAppear function of the original context aboutToAppearOrigin.apply(this, args); } }; } export { MockSetup, MockKit, when };