1/* 2 * Copyright (c) 2022 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"; 17import VerificationMode from "./VerificationMode"; 18import ArgumentMatchers from "./ArgumentMatchers"; 19 20class MockKit { 21 22 constructor() { 23 this.mFunctions = []; 24 this.stubs = new Map(); 25 this.recordCalls = new Map(); 26 this.currentSetKey = null; 27 this.mockObj = null; 28 this.recordMockedMethod = new Map(); 29 } 30 31 init() { 32 this.reset(); 33 } 34 35 reset() { 36 this.mFunctions = []; 37 this.stubs = {}; 38 this.recordCalls = {}; 39 this.currentSetKey = null; 40 this.mockObj = null; 41 this.recordMockedMethod = new Map(); 42 } 43 44 clearAll() { 45 this.reset(); 46 var props = Object.keys(this); 47 for (var i = 0; i < props.length; i++) { 48 delete this[props[i]]; 49 } 50 51 var props = Object.getOwnPropertyNames(this); 52 for (var i = 0; i < props.length; i++) { 53 delete this[props[i]]; 54 } 55 for (var key in this) { 56 delete this[key]; 57 } 58 } 59 60 clear(obj) { 61 if (!obj) throw Error("Please enter an object to be cleaned"); 62 if (typeof (obj) != 'object') throw new Error('Not a object'); 63 this.recordMockedMethod.forEach(function (value, key, map) { 64 if (key) { 65 obj[key] = value; 66 } 67 }); 68 } 69 70 ignoreMock(obj, method) { 71 if (typeof (obj) != 'object') throw new Error('Not a object'); 72 if (typeof (method) != 'function') throw new Error('Not a function'); 73 let og = this.recordMockedMethod.get(method.propName); 74 if (og) { 75 obj[method.propName] = og; 76 this.recordMockedMethod.set(method.propName, undefined); 77 } 78 } 79 80 extend(dest, source) { 81 dest["stub"] = source["stub"]; 82 dest["afterReturn"] = source["afterReturn"]; 83 dest["afterReturnNothing"] = source["afterReturnNothing"]; 84 dest["afterAction"] = source["afterAction"]; 85 dest["afterThrow"] = source["afterThrow"]; 86 dest["stubMockedCall"] = source["stubMockedCall"]; 87 dest["clear"] = source["clear"]; 88 return dest; 89 } 90 91 stubApply(f, params, returnInfo) { 92 let values = this.stubs.get(f); 93 if (!values) { 94 values = new Map(); 95 } 96 let key = params[0]; 97 if (typeof key == "undefined") { 98 key = "anonymous-mock-" + f.propName; 99 } 100 let matcher = new ArgumentMatchers(); 101 if (matcher.matcheStubKey(key)) { 102 key = matcher.matcheStubKey(key); 103 if (key) { 104 this.currentSetKey = key; 105 } 106 } 107 values.set(key, returnInfo); 108 this.stubs.set(f, values); 109 } 110 111 getReturnInfo(f, params) { 112 let values = this.stubs.get(f); 113 if (!values) { 114 return undefined; 115 } 116 let retrunKet = params[0]; 117 if (typeof retrunKet == "undefined") { 118 retrunKet = "anonymous-mock-" + f.propName; 119 } 120 let stubSetKey = this.currentSetKey; 121 122 if (this.currentSetKey && (typeof (retrunKet) != "undefined")) { 123 retrunKet = stubSetKey; 124 } 125 let matcher = new ArgumentMatchers(); 126 if (matcher.matcheReturnKey(params[0], undefined, stubSetKey) && matcher.matcheReturnKey(params[0], undefined, stubSetKey) != stubSetKey) { 127 retrunKet = params[0]; 128 } 129 130 values.forEach(function (value, key, map) { 131 if (ArgumentMatchers.isRegExp(key) && matcher.matcheReturnKey(params[0], key)) { 132 retrunKet = key; 133 } 134 }); 135 136 return values.get(retrunKet); 137 } 138 139 findName(obj, value) { 140 let properties = this.findProperties(obj); 141 let name = null; 142 properties.forEach( 143 function (va1, idx, array) { 144 if (obj[va1] === value) { 145 name = va1; 146 } 147 } 148 ); 149 return name; 150 } 151 152 isFunctionFromPrototype(f, container, propName) { 153 if (container.constructor != Object && container.constructor.prototype !== container) { 154 return container.constructor.prototype[propName] === f; 155 } 156 return false; 157 } 158 159 findProperties(obj, ...arg) { 160 function getProperty(new_obj) { 161 if (new_obj.__proto__ === null) { 162 return []; 163 } 164 let properties = Object.getOwnPropertyNames(new_obj); 165 return [...properties, ...getProperty(new_obj.__proto__)]; 166 } 167 return getProperty(obj); 168 } 169 170 recordMethodCall(originalMethod, args) { 171 Function.prototype.getName = function () { 172 return this.name || this.toString().match(/function\s*([^(]*)\(/)[1]; 173 }; 174 let name = originalMethod.getName(); 175 let arglistString = name + '(' + Array.from(args).toString() + ')'; 176 let records = this.recordCalls.get(arglistString); 177 if (!records) { 178 records = 0; 179 } 180 records++; 181 this.recordCalls.set(arglistString, records); 182 } 183 184 mockFunc(originalObject, originalMethod) { 185 let tmp = this; 186 this.originalMethod = originalMethod; 187 let f = function () { 188 let args = arguments; 189 let action = tmp.getReturnInfo(f, args); 190 if (originalMethod) { 191 tmp.recordMethodCall(originalMethod, args); 192 } 193 if (action) { 194 return action.apply(this, args); 195 } 196 }; 197 198 f.container = null || originalObject; 199 f.original = originalMethod || null; 200 201 if (originalObject && originalMethod) { 202 if (typeof (originalMethod) != 'function') throw new Error('Not a function'); 203 var name = this.findName(originalObject, originalMethod); 204 originalObject[name] = f; 205 this.recordMockedMethod.set(name, originalMethod); 206 f.propName = name; 207 f.originalFromPrototype = this.isFunctionFromPrototype(f.original, originalObject, f.propName); 208 } 209 f.mocker = this; 210 this.mFunctions.push(f); 211 this.extend(f, new ExtendInterface(this)); 212 return f; 213 } 214 215 verify(methodName, argsArray) { 216 if (!methodName) { 217 throw Error("not a function name"); 218 } 219 let a = this.recordCalls.get(methodName + '(' + argsArray.toString() + ')'); 220 return new VerificationMode(a ? a : 0); 221 } 222 223 mockObject(object) { 224 if (!object || typeof object === "string") { 225 throw Error(`this ${object} cannot be mocked`); 226 } 227 const _this = this; 228 let mockedObject = {}; 229 let keys = Reflect.ownKeys(object); 230 keys.filter(key => (typeof Reflect.get(object, key)) === 'function') 231 .forEach(key => { 232 mockedObject[key] = object[key]; 233 mockedObject[key] = _this.mockFunc(mockedObject, mockedObject[key]); 234 }); 235 return mockedObject; 236 } 237} 238 239function ifMockedFunction(f) { 240 if (Object.prototype.toString.call(f) != "[object Function]" && 241 Object.prototype.toString.call(f) != "[object AsyncFunction]") { 242 throw Error("not a function"); 243 } 244 if (!f.stub) { 245 throw Error("not a mock function"); 246 } 247 return true; 248} 249 250function when(f) { 251 if (ifMockedFunction(f)) { 252 return f.stub.bind(f); 253 } 254} 255 256export {MockKit, when};