1/* 2 * Copyright (c) 2022-2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import ExtendInterface from './ExtendInterface.js'; 17import VerificationMode from './VerificationMode.js'; 18import { ArgumentMatchers } from './ArgumentMatchers.js'; 19 20class MockKit { 21 constructor() { 22 this.mFunctions = []; 23 this.stubs = new Map(); 24 this.recordCalls = new Map(); 25 this.currentSetKey = new Map(); 26 this.mockObj = null; 27 this.recordMockedMethod = new Map(); 28 this.mFunctions = []; 29 this.stubs = new Map(); 30 this.recordCalls = new Map(); 31 this.currentSetKey = new Map(); 32 this.mockObj = null; 33 this.recordMockedMethod = new Map(); 34 } 35 init() { 36 this.reset(); 37 } 38 reset() { 39 this.mFunctions = []; 40 this.stubs = new Map(); 41 this.recordCalls = new Map(); 42 this.currentSetKey = new Map(); 43 this.mockObj = null; 44 this.recordMockedMethod = new Map(); 45 } 46 clearAll() { 47 this.reset(); 48 } 49 clear(obj) { 50 if (!obj) { 51 throw Error('Please enter an object to be cleaned'); 52 } 53 if (typeof (obj) !== 'object' && typeof (obj) !== 'function') { 54 throw new Error('Not a object or static class'); 55 } 56 this.recordMockedMethod.forEach(function (value, key, map) { 57 if (key) { 58 obj[key] = value; 59 } 60 }); 61 } 62 ignoreMock(obj, method) { 63 if (typeof (obj) !== 'object' && typeof (obj) !== 'function') { 64 throw new Error('Not a object or static class'); 65 } 66 if (typeof (method) !== 'function') { 67 throw new Error('Not a function'); 68 } 69 let og = this.recordMockedMethod.get(method.propName); 70 if (og) { 71 obj[method.propName] = og; 72 this.recordMockedMethod.set(method.propName, undefined); 73 } 74 } 75 extend(dest, source) { 76 dest['stub'] = source['stub']; 77 dest['afterReturn'] = source['afterReturn']; 78 dest['afterReturnNothing'] = source['afterReturnNothing']; 79 dest['afterAction'] = source['afterAction']; 80 dest['afterThrow'] = source['afterThrow']; 81 dest['stubMockedCall'] = source['stubMockedCall']; 82 dest['clear'] = source['clear']; 83 return dest; 84 } 85 stubApply(f, params, returnInfo) { 86 let values = this.stubs.get(f); 87 if (!values) { 88 values = new Map(); 89 } 90 let key = params[0]; 91 if (typeof key == 'undefined') { 92 key = 'anonymous-mock-' + f.propName; 93 } 94 let matcher = new ArgumentMatchers(); 95 if (matcher.matcheStubKey(key)) { 96 key = matcher.matcheStubKey(key); 97 if (key) { 98 this.currentSetKey.set(f, key); 99 } 100 } 101 values.set(key, returnInfo); 102 this.stubs.set(f, values); 103 } 104 getReturnInfo(f, params) { 105 let values = this.stubs.get(f); 106 if (!values) { 107 return undefined; 108 } 109 let retrunKet = params[0]; 110 if (typeof retrunKet == 'undefined') { 111 retrunKet = 'anonymous-mock-' + f.propName; 112 } 113 let stubSetKey = this.currentSetKey.get(f); 114 115 if (stubSetKey && (typeof (retrunKet) !== 'undefined')) { 116 retrunKet = stubSetKey; 117 } 118 let matcher = new ArgumentMatchers(); 119 if (matcher.matcheReturnKey(params[0], undefined, stubSetKey) && matcher.matcheReturnKey(params[0], undefined, stubSetKey) !== stubSetKey) { 120 retrunKet = params[0]; 121 } 122 values.forEach(function (value, key, map) { 123 if (ArgumentMatchers.isRegExp(key) && matcher.matcheReturnKey(params[0], key)) { 124 retrunKet = key; 125 } 126 }); 127 return values.get(retrunKet); 128 } 129 findName(obj, value) { 130 let properties = this.findProperties(obj); 131 let name = ''; 132 properties.filter((item) => (item !== 'caller' && item !== 'arguments')).forEach(function (va1, idx, array) { 133 if (obj[va1] === value) { 134 name = va1; 135 } 136 }); 137 return name; 138 } 139 isFunctionFromPrototype(f, container, propName) { 140 if (container.constructor !== Object && container.constructor.prototype !== container) { 141 return container.constructor.prototype[propName] === f; 142 } 143 return false; 144 } 145 findProperties(obj, ...arg) { 146 function getProperty(newObj) { 147 if (newObj.__proto__ === null) { 148 return []; 149 } 150 let properties = Object.getOwnPropertyNames(newObj); 151 return [...properties, ...getProperty(newObj.__proto__)]; 152 } 153 return getProperty(obj); 154 } 155 recordMethodCall(originalMethod, args) { 156 originalMethod['getName'] = function () { 157 return this.name || this.toString().match(/function\s*([^(]*)\(/)[1]; 158 }; 159 let name = originalMethod.getName(); 160 let arglistString = name + '(' + Array.from(args).toString() + ')'; 161 let records = this.recordCalls.get(arglistString); 162 if (!records) { 163 records = 0; 164 } 165 records++; 166 this.recordCalls.set(arglistString, records); 167 } 168 mockFunc(originalObject, originalMethod) { 169 let tmp = this; 170 this.originalMethod = originalMethod; 171 const _this = this; 172 let f = function () { 173 let args = arguments; 174 let action = tmp.getReturnInfo(f, args); 175 if (originalMethod) { 176 tmp.recordMethodCall(originalMethod, args); 177 } 178 if (action) { 179 return action.apply(_this, args); 180 } 181 }; 182 f.container = null || originalObject; 183 f.original = originalMethod || null; 184 if (originalObject && originalMethod) { 185 if (typeof (originalMethod) !== 'function') { 186 throw new Error('Not a function'); 187 } 188 var name = this.findName(originalObject, originalMethod); 189 originalObject[name] = f; 190 this.recordMockedMethod.set(name, originalMethod); 191 f.propName = name; 192 f.originalFromPrototype = this.isFunctionFromPrototype(f.original, originalObject, f.propName); 193 } 194 f.mocker = this; 195 this.mFunctions.push(f); 196 this.extend(f, new ExtendInterface(this)); 197 return f; 198 } 199 verify(methodName, argsArray) { 200 if (!methodName) { 201 throw Error('not a function name'); 202 } 203 let a = this.recordCalls.get(methodName + '(' + argsArray.toString() + ')'); 204 return new VerificationMode(a ? a : 0); 205 } 206 mockObject(object) { 207 if (!object || typeof object === 'string') { 208 throw Error(`this ${object} cannot be mocked`); 209 } 210 const _this = this; 211 let mockedObject = {}; 212 let keys = Reflect.ownKeys(object); 213 keys.filter(key => (typeof Reflect.get(object, key)) === 'function') 214 .forEach((key) => { 215 mockedObject[key] = object[key]; 216 mockedObject[key] = _this.mockFunc(mockedObject, mockedObject[key]); 217 }); 218 return mockedObject; 219 } 220} 221function ifMockedFunction(f) { 222 if (Object.prototype.toString.call(f) !== '[object Function]' && 223 Object.prototype.toString.call(f) !== '[object AsyncFunction]') { 224 throw Error('not a function'); 225 } 226 if (!f.stub) { 227 throw Error('not a mock function'); 228 } 229 return true; 230} 231function when(f) { 232 if (!ifMockedFunction(f)) { 233 throw Error('not a mock function'); 234 } 235 return f.stub.bind(f); 236} 237function MockSetup(target, propertyName, descriptor) { 238 const aboutToAppearOrigin = target.aboutToAppear; 239 const setup = descriptor.value; 240 target.aboutToAppear = function (...args) { 241 if (target.__Param) { // copy attributes and params of the original context 242 try { 243 const map = target.__Param; 244 for (const [key, val] of map) { 245 this[key] = val; // 'this' refers to context of current function 246 } 247 } 248 catch (e) { 249 throw new Error(`Mock setup param error: ${e}`); 250 } 251 } 252 if (setup) { // apply the mock content 253 try { 254 setup.apply(this); 255 } 256 catch (e) { 257 throw new Error(`Mock setup apply error: ${e}`); 258 } 259 } 260 if (aboutToAppearOrigin) { // append to aboutToAppear function of the original context 261 aboutToAppearOrigin.apply(this, args); 262 } 263 }; 264} 265export { MockSetup, MockKit, when }; 266