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