• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  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  }
29
30  init() {
31    this.reset();
32  }
33
34  reset() {
35    this.mFunctions = [];
36    this.stubs = {};
37    this.recordCalls = {};
38    this.currentSetKey = new Map();
39    this.mockObj = null;
40    this.recordMockedMethod = new Map();
41  }
42
43  clearAll() {
44    this.reset();
45    var props = Object.keys(this);
46    for (var i = 0; i < props.length; i++) {
47      delete this[props[i]];
48    }
49
50    var props = Object.getOwnPropertyNames(this);
51    for (var i = 0; i < props.length; i++) {
52      delete this[props[i]];
53    }
54    for (var key in this) {
55      delete this[key];
56    }
57  }
58
59  clear(obj) {
60    if (!obj) {
61      throw Error('Please enter an object to be cleaned');
62    }
63    if (typeof obj !== 'object' && typeof obj !== 'function') {
64      throw new Error('Not a object or static class');
65    }
66    this.recordMockedMethod.forEach(function (value, key, map) {
67      if (key) {
68        obj[key] = value;
69      }
70    });
71  }
72
73  ignoreMock(obj, method) {
74    if (typeof obj !== 'object' && typeof obj !== 'function') {
75      throw new Error('Not a object or static class');
76    }
77    if (typeof method !== 'function') {
78      throw new Error('Not a function');
79    }
80    let og = this.recordMockedMethod.get(method.propName);
81    if (og) {
82      obj[method.propName] = og;
83      this.recordMockedMethod.set(method.propName, undefined);
84    }
85  }
86
87  extend(dest, source) {
88    dest['stub'] = source['stub'];
89    dest['afterReturn'] = source['afterReturn'];
90    dest['afterReturnNothing'] = source['afterReturnNothing'];
91    dest['afterAction'] = source['afterAction'];
92    dest['afterThrow'] = source['afterThrow'];
93    dest['stubMockedCall'] = source['stubMockedCall'];
94    dest['clear'] = source['clear'];
95    return dest;
96  }
97
98  stubApply(f, params, returnInfo) {
99    let values = this.stubs.get(f);
100    if (!values) {
101      values = new Map();
102    }
103    let key = params[0];
104    if (typeof key === 'undefined') {
105      key = 'anonymous-mock-' + f.propName;
106    } else {
107      key = [];
108      const matcher = new ArgumentMatchers();
109      for (let i = 0; i < params.length; i++) {
110        const param = params[i];
111        const matchKey = matcher.matcheStubKey(param);
112        if (matchKey) {
113          key.push(matchKey);
114        } else {
115          key.push(param);
116        }
117      }
118    }
119    this.currentSetKey.set(f, key);
120    values.set(key, returnInfo);
121    this.stubs.set(f, values);
122  }
123
124  getReturnInfo(f, params) {
125    let values = this.stubs.get(f);
126    if (!values) {
127      return undefined;
128    }
129    let returnKet = params[0];
130    const anonymousName = 'anonymous-mock-' + f.propName;
131    if (typeof returnKet === 'undefined') {
132      returnKet = anonymousName;
133      let stubSetKey = this.currentSetKey.get(f);
134
135      if (stubSetKey && (typeof (returnKet) !== 'undefined')) {
136        returnKet = stubSetKey;
137      }
138    } else {
139      returnKet = this.getReturnKet(values, params, returnKet, anonymousName);
140    }
141
142    return values.get(returnKet);
143  }
144
145  getReturnKet(values, params, defaultValue, anonymousName) {
146    let flag = true;
147    let returnKet = defaultValue;
148    values.forEach((value, paramsKey, map) => {
149      if (
150        flag &&
151        paramsKey !== anonymousName &&
152        paramsKey.length === params.length &&
153        this.checkIsRightValue(paramsKey, params)
154      ) {
155        returnKet = paramsKey;
156        flag = false;
157      }
158    });
159    return returnKet;
160  }
161
162  checkIsRightValue(paramsKey, params) {
163    const matcher = new ArgumentMatchers();
164    return paramsKey.every((key, j) => {
165      if (ArgumentMatchers.isRegExp(key) && typeof params[j] === 'string') {
166        return key.test(params[j]);
167      }
168      const matchKey = matcher.matcheReturnKey(params[j], undefined, key);
169      if (matchKey && matchKey === key) {
170        return true;
171      } else if (this.checkIsEqual(params[j], key)) {
172        return true;
173      }
174      return false;
175    });
176  }
177
178  checkIsEqual(value1, value2) {
179    if (value1 === value2) {
180      return true;
181    }
182    if (typeof value1 !== typeof value2) {
183      return false;
184    }
185    if (Array.isArray(value1) && Array.isArray(value2)) {
186      if (value1.length !== value2.length) {
187        return false;
188      }
189      for (let i = 0; i < value1.length; i++) {
190        if (!this.checkIsEqual(value1[i], value2[i])) {
191          return false;
192        }
193      }
194      return true;
195    }
196    if (Object.prototype.toString.call(value1) === '[object Object]' &&
197      Object.prototype.toString.call(value2) === '[object Object]') {
198      const keys1 = Object.keys(value1);
199      const keys2 = Object.keys(value2);
200      if (keys1.length !== keys2.length) {
201        return false;
202      }
203      for (let key of keys1) {
204        if (!keys2.includes(key) || !this.checkIsEqual(value1[key], value2[key])) {
205          return false;
206        }
207      }
208      return true;
209    }
210    if (Object.prototype.toString.call(value1) === '[object Date]' &&
211      Object.prototype.toString.call(value2) === '[object Date]' &&
212      value1.getTime() === value2.getTime()) {
213      return true;
214    }
215    if (Object.prototype.toString.call(value1) === '[object RegExp]' &&
216      Object.prototype.toString.call(value2) === '[object RegExp]' &&
217      value1.toString() === value2.toString()) {
218      return true;
219    }
220    return false;
221  }
222
223  findName(obj, value) {
224    let properties = this.findProperties(obj);
225    let name = null;
226    properties
227      .filter((item) => item !== 'caller' && item !== 'arguments')
228      .forEach(function (va1, idx, array) {
229        if (obj[va1] === value) {
230          name = va1;
231        }
232      });
233    return name;
234  }
235
236  isFunctionFromPrototype(f, container, propName) {
237    if (
238      container.constructor !== Object &&
239      container.constructor.prototype !== container
240    ) {
241      return container.constructor.prototype[propName] === f;
242    }
243    return false;
244  }
245
246  findProperties(obj, ...arg) {
247    function getProperty(newObj) {
248      if (newObj.__proto__ === null) {
249        return [];
250      }
251      let properties = Object.getOwnPropertyNames(newObj);
252      return [...properties, ...getProperty(newObj.__proto__)];
253    }
254    return getProperty(obj);
255  }
256
257  recordMethodCall(originalMethod, args) {
258    Function.prototype.getName = function () {
259      return this.name || this.toString().match(/function\s*([^(]*)\(/)[1];
260    };
261    let name = originalMethod.getName();
262    let arglistString = name + '(' + Array.from(args).toString() + ')';
263    let records = this.recordCalls.get(arglistString);
264    if (!records) {
265      records = 0;
266    }
267    records++;
268    this.recordCalls.set(arglistString, records);
269  }
270
271  mockFunc(originalObject, originalMethod) {
272    let tmp = this;
273    this.originalMethod = originalMethod;
274    let f = function () {
275      let args = arguments;
276      let action = tmp.getReturnInfo(f, args);
277      if (originalMethod) {
278        tmp.recordMethodCall(originalMethod, args);
279      }
280      if (action) {
281        return action.apply(this, args);
282      }
283    };
284
285    f.container = null || originalObject;
286    f.original = originalMethod || null;
287
288    if (originalObject && originalMethod) {
289      if (typeof originalMethod !== 'function') {
290        throw new Error('Not a function');
291      }
292      var name = this.findName(originalObject, originalMethod);
293      originalObject[name] = f;
294      this.recordMockedMethod.set(name, originalMethod);
295      f.propName = name;
296      f.originalFromPrototype = this.isFunctionFromPrototype(
297        f.original,
298        originalObject,
299        f.propName
300      );
301    }
302    f.mocker = this;
303    this.mFunctions.push(f);
304    this.extend(f, new ExtendInterface(this));
305    return f;
306  }
307
308  verify(methodName, argsArray) {
309    if (!methodName) {
310      throw Error('not a function name');
311    }
312    let a = this.recordCalls.get(methodName + '(' + argsArray.toString() + ')');
313    return new VerificationMode(a ? a : 0);
314  }
315
316  mockObject(object) {
317    if (!object || typeof object === 'string') {
318      throw Error(`this ${object} cannot be mocked`);
319    }
320    const _this = this;
321    let mockedObject = {};
322    let keys = Reflect.ownKeys(object);
323    keys
324      .filter((key) => typeof Reflect.get(object, key) === 'function')
325      .forEach((key) => {
326        mockedObject[key] = object[key];
327        mockedObject[key] = _this.mockFunc(mockedObject, mockedObject[key]);
328      });
329    return mockedObject;
330  }
331}
332
333function ifMockedFunction(f) {
334  if (
335    Object.prototype.toString.call(f) !== '[object Function]' &&
336    Object.prototype.toString.call(f) !== '[object AsyncFunction]'
337  ) {
338    throw Error('not a function');
339  }
340  if (!f.stub) {
341    throw Error('not a mock function');
342  }
343  return true;
344}
345
346function when(f) {
347  if (!ifMockedFunction(f)) {
348    throw Error('not a mock function');
349  }
350  return f.stub.bind(f);
351}
352
353export { MockKit, when };
354