/* * Copyright (c) 2022 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"; import VerificationMode from "./VerificationMode"; import ArgumentMatchers from "./ArgumentMatchers"; class MockKit { constructor() { this.mFunctions = []; this.stubs = new Map(); this.recordCalls = new Map(); this.currentSetKey = null; this.mockObj = null; this.recordMockedMethod = new Map(); } init() { this.reset(); } reset() { this.mFunctions = []; this.stubs = {}; this.recordCalls = {}; this.currentSetKey = null; this.mockObj = null; this.recordMockedMethod = new Map(); } clearAll() { this.reset(); var props = Object.keys(this); for (var i = 0; i < props.length; i++) { delete this[props[i]]; } var props = Object.getOwnPropertyNames(this); for (var i = 0; i < props.length; i++) { delete this[props[i]]; } for (var key in this) { delete this[key]; } } clear(obj) { if (!obj) throw Error("Please enter an object to be cleaned"); if (typeof (obj) != 'object') throw new Error('Not a object'); this.recordMockedMethod.forEach(function (value, key, map) { if (key) { obj[key] = value; } }); } ignoreMock(obj, method) { if (typeof (obj) != 'object') throw new Error('Not a object'); 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 = 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; if (this.currentSetKey && (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 = null; properties.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(new_obj) { if (new_obj.__proto__ === null) { return []; } let properties = Object.getOwnPropertyNames(new_obj); return [...properties, ...getProperty(new_obj.__proto__)]; } return getProperty(obj); } recordMethodCall(originalMethod, args) { Function.prototype.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; 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)) { return f.stub.bind(f); } } export {MockKit, when};